{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EheA5_j_cEwc"
      },
      "source": [
        "##### Copyright 2021 Google LLC.\n",
        "\n",
        "Licensed under the Apache License, Version 2.0 (the \"License\");"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YCriMWd-pRTP"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\"); { display-mode: \"form\" }\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LDA0UwzhdJMo"
      },
      "outputs": [],
      "source": [
        "!pip install tf-quant-finance"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 8029,
          "status": "ok",
          "timestamp": 1627045868367,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "4Lc3St-vfXHu",
        "outputId": "139bc321-5ecf-4a35-bc92-c956a4f9662a"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Collecting QuantLib-Python\n",
            "  Downloading QuantLib_Python-1.18-py2.py3-none-any.whl (1.4 kB)\n",
            "Collecting QuantLib\n",
            "  Downloading QuantLib-1.23-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (20.1 MB)\n",
            "\u001b[K     |████████████████████████████████| 20.1 MB 62.7 MB/s \n",
            "\u001b[?25hInstalling collected packages: QuantLib, QuantLib-Python\n",
            "Successfully installed QuantLib-1.23 QuantLib-Python-1.18\n"
          ]
        }
      ],
      "source": [
        "!pip install QuantLib-Python"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WB1flXxowzyN"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "import numpy as np\n",
        "# tf.compat.v1.disable_eager_execution()\n",
        "from typing import Any, Dict, List, Tuple, Union, Optional\n",
        "import dataclasses\n",
        "from tf_quant_finance import datetime as dates\n",
        "from tf_quant_finance.models import generic_ito_process\n",
        "from tf_quant_finance.math import piecewise\n",
        "from tf_quant_finance.math import random\n",
        "\n",
        "from tf_quant_finance.math import gradient\n",
        "from tf_quant_finance.models import euler_sampling\n",
        "from tf_quant_finance.models import utils\n",
        "from tf_quant_finance.models.hjm import quasi_gaussian_hjm\n",
        "from tf_quant_finance.math.interpolation.linear import interpolate\n",
        "from tf_quant_finance.math.pde import fd_solvers\n",
        "\n",
        "import matplotlib.pyplot as plt"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3d32xJoMx9lp"
      },
      "source": [
        "# HJM Model"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "F-Bp6OBxx6aA"
      },
      "outputs": [],
      "source": [
        "class GaussianHJM(quasi_gaussian_hjm.QuasiGaussianHJM):\n",
        "  r\"\"\"Gaussian HJM model for term-structure modeling.\n",
        "\n",
        "  Heath-Jarrow-Morton (HJM) model for the interest rate term-structre\n",
        "  modelling specifies the dynamics of the instantaneus forward rate `f(t,T)`\n",
        "  with maturity `T` at time `t` as follows:\n",
        "\n",
        "  ```None\n",
        "    df(t,T) = mu(t,T) dt + sum_i sigma_i(t,  T) * dW_i(t),\n",
        "    1 \u003c= i \u003c= n,\n",
        "  ```\n",
        "  where `mu(t,T)` and `sigma_i(t,T)` denote the drift and volatility\n",
        "  for the forward rate and `W_i` are Brownian motions with instantaneous\n",
        "  correlation `Rho`. The model above represents an `n-factor` HJM model.\n",
        "  The Gaussian HJM model assumes that the volatility `sigma_i(t,T)` is a\n",
        "  deterministic function of time (t). Under the risk-neutral measure, the\n",
        "  drift `mu(t,T)` is computed as\n",
        "\n",
        "  ```\n",
        "    mu(t,T) = sum_i sigma_i(t,T)  int_t^T sigma_(t,u) du\n",
        "  ```\n",
        "  Using the separability condition, the HJM model above can be formulated as\n",
        "  the following Markovian model:\n",
        "\n",
        "  ```None\n",
        "    sigma(t,T) = sigma(t) * h(T)    (Separability condition)\n",
        "  ```\n",
        "  A common choice for the function h(t) is `h(t) = exp(-kt)`. Using the above\n",
        "  parameterization of sigma(t,T), we obtain the following Markovian\n",
        "  formulation of the HJM model [1]:\n",
        "\n",
        "  ```None\n",
        "    HJM Model\n",
        "    dx_i(t) = (sum_j [y_ij(t)] - k_i * x_i(t)) dt + sigma_i(t) dW_i\n",
        "    dy_ij(t) = (rho_ij * sigma_i(t)*sigma_j(t) - (k_i + k_j) * y_ij(t)) dt\n",
        "    r(t) = sum_i x_i(t) + f(0, t)\n",
        "  ```\n",
        "  where `x` is an `n`-dimensional vector and `y` is an `nxn` dimensional\n",
        "  matrix. For Gaussian HJM model, the quantity `y_ij(t)` can be computed\n",
        "  analytically as follows:\n",
        "\n",
        "  ```None\n",
        "    y_ij(t) = rho_ij * exp(-k_i * t) * exp(-k_j * t) *\n",
        "              int_0^t exp((k_i+k_j) * s) * sigma_i(s) * sigma_j(s) ds\n",
        "  ```\n",
        "\n",
        "  The Gaussian HJM class implements the model outlined above by simulating the\n",
        "  state `x(t)` while analytically computing `y(t)`.\n",
        "\n",
        "  The price at time `t` of a zero-coupon bond maturing at `T` is given by\n",
        "  (Ref. [1]):\n",
        "\n",
        "  ```None\n",
        "  P(t,T) = P(0,T) / P(0,t) *\n",
        "           exp(-x(t) * G(t,T) - 0.5 * y(t) * G(t,T)^2)\n",
        "  ```\n",
        "\n",
        "  The HJM model implementation supports constant mean-reversion rate `k` and\n",
        "  `sigma(t)` can be an arbitrary function of `t`. We use Euler discretization\n",
        "  to simulate the HJM model.\n",
        "\n",
        "  #### Example. Simulate a 4-factor HJM process.\n",
        "\n",
        "  ```python\n",
        "  import numpy as np\n",
        "  import tensorflow.compat.v2 as tf\n",
        "  import tf_quant_finance as tff\n",
        "\n",
        "  dtype = tf.float64\n",
        "  def discount_fn(x):\n",
        "    return 0.01 * tf.ones_like(x, dtype=dtype)\n",
        "\n",
        "  process = tff.models.hjm.GaussianHJM(\n",
        "      dim=4,\n",
        "      mean_reversion=[0.03, 0.01, 0.02, 0.005],  # constant mean-reversion\n",
        "      volatility=[0.01, 0.011, 0.015, 0.008],  # constant volatility\n",
        "      initial_discount_rate_fn=discount_fn,\n",
        "      dtype=dtype)\n",
        "  times = np.array([0.1, 1.0, 2.0, 3.0])\n",
        "  short_rate_paths, discount_paths, _, _ = process.sample_paths(\n",
        "      times,\n",
        "      num_samples=100000,\n",
        "      time_step=0.1,\n",
        "      random_type=tff.math.random.RandomType.STATELESS_ANTITHETIC,\n",
        "      seed=[1, 2],\n",
        "      skip=1000000)\n",
        "  ```\n",
        "\n",
        "  #### References:\n",
        "    [1]: Leif B. G. Andersen and Vladimir V. Piterbarg. Interest Rate Modeling.\n",
        "    Volume II: Term Structure Models.\n",
        "  \"\"\"\n",
        "\n",
        "  def __init__(self,\n",
        "               dim,\n",
        "               mean_reversion,\n",
        "               volatility,\n",
        "               initial_discount_rate_fn,\n",
        "               corr_matrix=None,\n",
        "               dtype=None,\n",
        "               name=None):\n",
        "    \"\"\"Initializes the HJM model.\n",
        "\n",
        "    Args:\n",
        "      dim: A Python scalar which corresponds to the number of factors comprising\n",
        "        the model.\n",
        "      mean_reversion: A real positive `Tensor` of shape `[dim]`. Corresponds to\n",
        "        the mean reversion rate of each factor.\n",
        "      volatility: A real positive `Tensor` of the same `dtype` and shape as\n",
        "        `mean_reversion` or a callable with the following properties: (a)  The\n",
        "          callable should accept a scalar `Tensor` `t` and returns a 1-D\n",
        "          `Tensor` of shape `[dim]`. The function returns instantaneous\n",
        "          volatility `sigma(t)`. When `volatility` is specified is a real\n",
        "          `Tensor`, each factor is assumed to have a constant instantaneous\n",
        "          volatility. Corresponds to the instantaneous volatility of each\n",
        "          factor.\n",
        "      initial_discount_rate_fn: A Python callable that accepts expiry time as a\n",
        "        real `Tensor` of the same `dtype` as `mean_reversion` and returns a\n",
        "        `Tensor` of shape `input_shape`. Corresponds to the zero coupon bond\n",
        "        yield at the present time for the input expiry time.\n",
        "      corr_matrix: A `Tensor` of shape `[dim, dim]` and the same `dtype` as\n",
        "        `mean_reversion`. Corresponds to the correlation matrix `Rho`.\n",
        "      dtype: The default dtype to use when converting values to `Tensor`s.\n",
        "        Default value: `None` which means that default dtypes inferred by\n",
        "          TensorFlow are used.\n",
        "      name: Python string. The name to give to the ops created by this class.\n",
        "        Default value: `None` which maps to the default name\n",
        "          `gaussian_hjm_model`.\n",
        "    \"\"\"\n",
        "    self._name = name or 'gaussian_hjm_model'\n",
        "    with tf.name_scope(self._name):\n",
        "      self._dtype = dtype or None\n",
        "      self._dim = dim\n",
        "      self._factors = dim\n",
        "\n",
        "      def _instant_forward_rate_fn(t):\n",
        "        t = tf.convert_to_tensor(t, dtype=self._dtype)\n",
        "\n",
        "        def _log_zero_coupon_bond(x):\n",
        "          r = tf.convert_to_tensor(\n",
        "              initial_discount_rate_fn(x), dtype=self._dtype)\n",
        "          return -r * x\n",
        "\n",
        "        rate = -gradient.fwd_gradient(\n",
        "            _log_zero_coupon_bond,\n",
        "            t,\n",
        "            use_gradient_tape=True,\n",
        "            unconnected_gradients=tf.UnconnectedGradients.ZERO)\n",
        "        return rate\n",
        "\n",
        "      def _initial_discount_rate_fn(t):\n",
        "        return tf.convert_to_tensor(\n",
        "            initial_discount_rate_fn(t), dtype=self._dtype)\n",
        "\n",
        "      self._instant_forward_rate_fn = _instant_forward_rate_fn\n",
        "      self._initial_discount_rate_fn = _initial_discount_rate_fn\n",
        "      self._mean_reversion = tf.convert_to_tensor(\n",
        "          mean_reversion, dtype=dtype, name='mean_reversion')\n",
        "\n",
        "      self._batch_shape = []\n",
        "      self._batch_rank = 0\n",
        "\n",
        "      # Setup volatility\n",
        "      if callable(volatility):\n",
        "        self._volatility = volatility\n",
        "      else:\n",
        "        volatility = tf.convert_to_tensor(volatility, dtype=dtype)\n",
        "        jump_locations = [[]] * dim\n",
        "        volatility = tf.expand_dims(volatility, axis=-1)\n",
        "        self._volatility = piecewise.PiecewiseConstantFunc(\n",
        "            jump_locations=jump_locations, values=volatility, dtype=dtype)\n",
        "\n",
        "      if corr_matrix is None:\n",
        "        corr_matrix = tf.eye(dim, dim, dtype=self._dtype)\n",
        "      self._rho = tf.convert_to_tensor(corr_matrix, dtype=dtype, name='rho')\n",
        "      self._sqrt_rho = tf.linalg.cholesky(self._rho)\n",
        "\n",
        "      # Volatility function\n",
        "      def _vol_fn(t, state):\n",
        "        \"\"\"Volatility function of Gaussian-HJM.\"\"\"\n",
        "        del state\n",
        "        volatility = self._volatility(tf.expand_dims(t, -1))  # shape=(dim, 1)\n",
        "        print(t, volatility)\n",
        "\n",
        "        return self._sqrt_rho * volatility\n",
        "\n",
        "      # Drift function\n",
        "      def _drift_fn(t, state):\n",
        "        \"\"\"Drift function of Gaussian-HJM.\"\"\"\n",
        "        x = state\n",
        "        # shape = [self._factors, self._factors]\n",
        "        y = self.state_y(tf.expand_dims(t, axis=-1))[..., 0]\n",
        "        drift = tf.math.reduce_sum(y, axis=-1) - self._mean_reversion * x\n",
        "        return drift\n",
        "\n",
        "      self._exact_discretization_setup(dim)\n",
        "      super(quasi_gaussian_hjm.QuasiGaussianHJM,\n",
        "            self).__init__(dim, _drift_fn, _vol_fn, dtype, self._name)\n",
        "\n",
        "  def sample_paths(self,\n",
        "                   times,\n",
        "                   num_samples,\n",
        "                   time_step=None,\n",
        "                   num_time_steps=None,\n",
        "                   random_type=None,\n",
        "                   seed=None,\n",
        "                   skip=0,\n",
        "                   use_euler_sampling=False,\n",
        "                   name=None):\n",
        "    \"\"\"Returns a sample of short rate paths from the HJM process.\n",
        "\n",
        "    For the Gaussian HJM model, the distribution of short rate and spot discount\n",
        "    curve can be computed analytically. By default the method uses the\n",
        "    analytical distribution to generate sample paths for short rate. Euler\n",
        "    sampling can be enabled through an input flag.\n",
        "\n",
        "    Args:\n",
        "      times: A real positive `Tensor` of shape `(num_times,)`. The times at\n",
        "        which the path points are to be evaluated.\n",
        "      num_samples: Positive scalar `int32` `Tensor`. The number of paths to\n",
        "        draw.\n",
        "      time_step: Scalar real `Tensor`. Maximal distance between time grid points\n",
        "        in Euler scheme. Used only when Euler scheme is applied.\n",
        "        Default value: `None`.\n",
        "      num_time_steps: An optional Scalar integer `Tensor` - a total number of\n",
        "        time steps performed by the algorithm. The maximal distance betwen\n",
        "        points in grid is bounded by\n",
        "        `times[-1] / (num_time_steps - times.shape[0])`.\n",
        "        Either this or `time_step` should be supplied.\n",
        "        Default value: `None`.\n",
        "      random_type: Enum value of `RandomType`. The type of (quasi)-random\n",
        "        number generator to use to generate the paths.\n",
        "        Default value: `None` which maps to the standard pseudo-random numbers.\n",
        "      seed: Seed for the random number generator. The seed is\n",
        "        only relevant if `random_type` is one of\n",
        "        `[STATELESS, PSEUDO, HALTON_RANDOMIZED, PSEUDO_ANTITHETIC,\n",
        "          STATELESS_ANTITHETIC]`. For `PSEUDO`, `PSEUDO_ANTITHETIC` and\n",
        "        `HALTON_RANDOMIZED` the seed should be an Python integer. For\n",
        "        `STATELESS` and  `STATELESS_ANTITHETIC `must be supplied as an integer\n",
        "        `Tensor` of shape `[2]`.\n",
        "        Default value: `None` which means no seed is set.\n",
        "      skip: `int32` 0-d `Tensor`. The number of initial points of the Sobol or\n",
        "        Halton sequence to skip. Used only when `random_type` is 'SOBOL',\n",
        "        'HALTON', or 'HALTON_RANDOMIZED', otherwise ignored.\n",
        "        Default value: `0`.\n",
        "      use_euler_sampling: An optional Python boolean to indicate if simulations\n",
        "        are performed using Euler sampling.\n",
        "        Default value: `False` indicating exact sampling to be used.\n",
        "      name: Python string. The name to give this op.\n",
        "        Default value: `sample_paths`.\n",
        "\n",
        "    Returns:\n",
        "      A tuple containing four elements.\n",
        "\n",
        "      * The first element is a `Tensor` of\n",
        "      shape `[num_samples, num_times]` containing the simulated short rate\n",
        "      paths.\n",
        "      * The second element is a `Tensor` of shape\n",
        "      `[num_samples, num_times]` containing the simulated discount factor\n",
        "      paths.\n",
        "      * The third element is a `Tensor` of shape\n",
        "      `[num_samples, num_times, dim]` conating the simulated values of the\n",
        "      state variable `x`\n",
        "      * The fourth element is a `Tensor` of shape\n",
        "      `[num_samples, num_times, dim^2]` conating the simulated values of the\n",
        "      state variable `y`.\n",
        "\n",
        "    Raises:\n",
        "      ValueError:\n",
        "        (a) If `times` has rank different from `1`.\n",
        "        (b) If Euler scheme is used by times is not supplied.\n",
        "    \"\"\"\n",
        "    name = name or self._name + '_sample_path'\n",
        "    with tf.name_scope(name):\n",
        "      times = tf.convert_to_tensor(times, self._dtype)\n",
        "      if times.shape.rank != 1:\n",
        "        raise ValueError('`times` should be a rank 1 Tensor. '\n",
        "                         'Rank is {} instead.'.format(times.shape.rank))\n",
        "      return self._sample_paths(\n",
        "          times, time_step, num_time_steps, num_samples, random_type, skip,\n",
        "          seed, use_euler_sampling)\n",
        "\n",
        "  def state_y(self, t):\n",
        "    \"\"\"Computes the state variable `y(t)` for tha Gaussian HJM Model.\n",
        "\n",
        "    For Gaussian HJM model, the state parameter y(t), can be analytically\n",
        "    computed as follows:\n",
        "\n",
        "    y_ij(t) = exp(-k_i * t) * exp(-k_j * t) * (\n",
        "              int_0^t [exp(k_i * u) * exp(k_j * u) * rho_ij *\n",
        "                      sigma_i(u) * sigma_j(u)] * du)\n",
        "\n",
        "    Args:\n",
        "      t: A rank 1 real `Tensor` of shape `[num_times]` specifying the time `t`.\n",
        "\n",
        "    Returns:\n",
        "      A real `Tensor` of shape [self._factors, self._factors, num_times]\n",
        "      containing the computed y_ij(t).\n",
        "    \"\"\"\n",
        "    t = tf.convert_to_tensor(t, dtype=self._dtype)\n",
        "    # t_shape = tf.shape(t)\n",
        "    # t = tf.broadcast_to(t, tf.concat([[self._dim], t_shape], axis=0))\n",
        "    time_index = tf.searchsorted(self._jump_locations, t)\n",
        "    # create a matrix k2(i,j) = k(i) + k(j)\n",
        "    mr2 = tf.expand_dims(self._mean_reversion, axis=-1)\n",
        "    # Add a dimension corresponding to `num_times`\n",
        "    mr2 = tf.expand_dims(mr2 + tf.transpose(mr2), axis=-1)\n",
        "\n",
        "    def _integrate_volatility_squared(vol, l_limit, u_limit):\n",
        "      # create sigma2_ij = sigma_i * sigma_j\n",
        "      vol = tf.expand_dims(vol, axis=-2)\n",
        "      vol_squared = tf.expand_dims(self._rho, axis=-1) * (\n",
        "          vol * tf.transpose(vol, perm=[1, 0, 2]))\n",
        "      return vol_squared / mr2 * (tf.math.exp(mr2 * u_limit) - tf.math.exp(\n",
        "          mr2 * l_limit))\n",
        "\n",
        "    is_constant_vol = tf.math.equal(tf.shape(self._jump_values_vol)[-1], 0)\n",
        "    v_squared_between_vol_knots = tf.cond(\n",
        "        is_constant_vol,\n",
        "        lambda: tf.zeros(shape=(self._dim, self._dim, 0), dtype=self._dtype),\n",
        "        lambda: _integrate_volatility_squared(  # pylint: disable=g-long-lambda\n",
        "            self._jump_values_vol, self._padded_knots, self._jump_locations))\n",
        "    v_squared_at_vol_knots = tf.concat([\n",
        "        tf.zeros((self._dim, self._dim, 1), dtype=self._dtype),\n",
        "        utils.cumsum_using_matvec(v_squared_between_vol_knots)\n",
        "    ], axis=-1)\n",
        "\n",
        "    vn = tf.concat([self._zero_padding, self._jump_locations], axis=0)\n",
        "\n",
        "    v_squared_t = _integrate_volatility_squared(\n",
        "        self._volatility(t), tf.gather(vn, time_index), t)\n",
        "    v_squared_t += tf.gather(v_squared_at_vol_knots, time_index, batch_dims=-1)\n",
        "\n",
        "    return tf.math.exp(-mr2 * t) * v_squared_t\n",
        "\n",
        "  def discount_bond_price(self, state, times, maturities, name=None):\n",
        "    \"\"\"Returns zero-coupon bond prices `P(t,T)` conditional on `x(t)`.\n",
        "\n",
        "    Args:\n",
        "      state: A `Tensor` of real dtype and shape compatible with\n",
        "        `(num_times, dim)` specifying the state `x(t)`.\n",
        "      times: A `Tensor` of real dtype and shape `(num_times,)`. The time `t`\n",
        "        at which discount bond prices are computed.\n",
        "      maturities: A `Tensor` of real dtype and shape `(num_times,)`. The time\n",
        "        to maturity of the discount bonds.\n",
        "      name: Str. The name to give this op.\n",
        "        Default value: `discount_bond_prices`.\n",
        "\n",
        "    Returns:\n",
        "      A `Tensor` of real dtype and the same shape as `(num_times,)`\n",
        "      containing the price of zero-coupon bonds.\n",
        "    \"\"\"\n",
        "    name = name or self._name + '_discount_bond_prices'\n",
        "    with tf.name_scope(name):\n",
        "      x_t = tf.convert_to_tensor(state, self._dtype)\n",
        "      times = tf.convert_to_tensor(times, self._dtype)\n",
        "      maturities = tf.convert_to_tensor(maturities, self._dtype)\n",
        "      # Flatten it because `PiecewiseConstantFunction` expects the first\n",
        "      # dimension to be broadcastable to [dim]\n",
        "      input_shape_times = tf.shape(times)\n",
        "      # The shape of `mean_reversion` will is `[dim]`\n",
        "      mean_reversion = self._mean_reversion\n",
        "      y_t = self.state_y(times)\n",
        "\n",
        "      y_t = tf.reshape(tf.transpose(y_t), tf.concat(\n",
        "          [input_shape_times, [self._dim, self._dim]], axis=0))\n",
        "      # Shape=(1, 1, num_times)\n",
        "      values = self._bond_reconstitution(\n",
        "          times, maturities, mean_reversion, x_t, y_t, 1,\n",
        "          tf.shape(times)[0])\n",
        "      return values[0][0]\n",
        "\n",
        "  def _sample_paths(self, times, time_step, num_time_steps, num_samples,\n",
        "                    random_type, skip, seed, use_euler_sampling=False):\n",
        "    \"\"\"Returns a sample of paths from the process.\"\"\"\n",
        "    initial_state = tf.zeros((self._dim,), dtype=self._dtype)\n",
        "    if use_euler_sampling:\n",
        "      rate_paths, discount_factor_paths, paths = self._sample_paths_from_euler(\n",
        "          times, time_step, num_time_steps, num_samples, random_type, skip,\n",
        "          seed, initial_state)\n",
        "    else:\n",
        "      rate_paths, discount_factor_paths, paths = self._sample_paths_from_exact(\n",
        "          times, time_step, num_time_steps, num_samples, random_type,\n",
        "          skip, seed, initial_state)\n",
        "\n",
        "    y_paths = self.state_y(times)  # shape=(dim, dim, num_times)\n",
        "    y_paths = tf.reshape(\n",
        "        y_paths, tf.concat([[self._dim**2], tf.shape(times)], axis=0))\n",
        "\n",
        "    # shape=(num_samples, num_times, dim**2)\n",
        "    y_paths = tf.repeat(tf.expand_dims(tf.transpose(\n",
        "        y_paths), axis=0), num_samples, axis=0)\n",
        "    return (rate_paths, discount_factor_paths, paths, y_paths)\n",
        "\n",
        "  def _sample_paths_from_euler(\n",
        "      self, times, time_step, num_time_steps, num_samples, random_type, skip,\n",
        "      seed, initial_state):\n",
        "    \"\"\"Returns a sample of paths from the process using Euler sampling.\"\"\"\n",
        "    # Note that we need a finer simulation grid (determnied by `dt`) to compute\n",
        "    # discount factors accurately. The `times` input might not be granular\n",
        "    # enough for accurate calculations.\n",
        "    time_step_internal = time_step\n",
        "    if num_time_steps is not None:\n",
        "      num_time_steps = tf.convert_to_tensor(num_time_steps, dtype=tf.int32,\n",
        "                                            name='num_time_steps')\n",
        "      time_step_internal = times[-1] / tf.cast(\n",
        "          num_time_steps, dtype=self._dtype)\n",
        "\n",
        "    times, _, time_indices = utils.prepare_grid(\n",
        "        times=times, time_step=time_step_internal, dtype=self._dtype,\n",
        "        num_time_steps=num_time_steps)\n",
        "    # Add zeros as a starting location\n",
        "    dt = times[1:] - times[:-1]\n",
        "\n",
        "    # Shape = (num_samples, num_times, nfactors)\n",
        "    paths = euler_sampling.sample(\n",
        "        self._dim,\n",
        "        self._drift_fn,\n",
        "        self._volatility_fn,\n",
        "        times,\n",
        "        num_time_steps=num_time_steps,\n",
        "        num_samples=num_samples,\n",
        "        initial_state=initial_state,\n",
        "        random_type=random_type,\n",
        "        seed=seed,\n",
        "        time_step=time_step,\n",
        "        skip=skip)\n",
        "    \n",
        "    f_0_t = self._instant_forward_rate_fn(times)  # shape=(num_times,)\n",
        "    rate_paths = tf.math.reduce_sum(\n",
        "        paths, axis=-1) + f_0_t  # shape=(num_samples, num_times)\n",
        "\n",
        "    discount_factor_paths = tf.math.exp(-rate_paths[:, :-1] * dt)\n",
        "    discount_factor_paths = tf.concat(\n",
        "        [tf.ones((num_samples, 1), dtype=self._dtype), discount_factor_paths],\n",
        "        axis=1)  # shape=(num_samples, num_times)\n",
        "    discount_factor_paths = utils.cumprod_using_matvec(discount_factor_paths)\n",
        "    return (tf.gather(rate_paths, time_indices, axis=1),\n",
        "            tf.gather(discount_factor_paths, time_indices, axis=1),\n",
        "            tf.gather(paths, time_indices, axis=1))\n",
        "    \n",
        "  def _sample_paths_from_exact(\n",
        "      self, times, time_step, num_time_steps, num_samples, random_type, skip,\n",
        "      seed, initial_state):\n",
        "    \"\"\"Returns a sample of paths from the process using exact sampling.\"\"\"\n",
        "    num_requested_times = tf.shape(times)[0]\n",
        "    # Add zeros as a starting location\n",
        "    times = tf.concat([[0.], times], axis=0)\n",
        "    keep_mask = tf.cast(tf.concat([[0], tf.ones((num_requested_times), dtype=tf.int32)], axis=0), tf.bool)\n",
        "    \n",
        "    dt = times[1:] - times[:-1]\n",
        "    if dt.shape.is_fully_defined():\n",
        "      steps_num = dt.shape.as_list()[-1]\n",
        "    else:\n",
        "      steps_num = tf.shape(dt)[-1]\n",
        "      # TODO(b/148133811): Re-enable Sobol test when TF 2.2 is released.\n",
        "      if random_type == random.RandomType.SOBOL:\n",
        "        raise ValueError('Sobol sequence for sample paths is temporarily '\n",
        "                         'unsupported when `time_step` or `times` have a '\n",
        "                         'non-constant value')\n",
        "    # We generate `dim + 1` draws with an additonal draw for discount factor\n",
        "    normal_draws = utils.generate_mc_normal_draws(\n",
        "        num_normal_draws=self._dim + 1, num_time_steps=steps_num,\n",
        "        num_sample_paths=num_samples, random_type=random_type,\n",
        "        seed=seed,\n",
        "        dtype=self._dtype, skip=skip)\n",
        "    \n",
        "    exp_x_t = self._conditional_mean_x(times)\n",
        "    var_x_t = self._conditional_variance_x(times)\n",
        "    log_df_double_integral = self._discount_factor_double_integral(times)\n",
        "    \n",
        "    y = self.state_y(times)\n",
        "    cov_x_log_df2 = self._cov_between_x_and_log_df(times)\n",
        "    cov_x_log_df = -cov_x_log_df2# tf.zeros_like(cov_x_log_df2)\n",
        "\n",
        "    cond_fn = lambda i, *args: i \u003c steps_num\n",
        "    def body_fn(i, written_count, current_x, current_log_df, x_paths, log_df_paths):\n",
        "      \"\"\"Simulate HJM process to the next time point.\"\"\"\n",
        "      normals = normal_draws[i]\n",
        "\n",
        "      # Update log discount factor I(t) = -int_0^t sum(x_i(t))\n",
        "      # G(t_i, t_i+1)\n",
        "      capital_g = (1. - tf.math.exp(\n",
        "        -self._mean_reversion * (times[i+1] - times[i]))) / self._mean_reversion\n",
        "      y_times_g_squared = tf.math.reduce_sum(tf.linalg.matvec(y[..., i], capital_g) * capital_g)\n",
        "\n",
        "      cov_matrix = tf.concat(\n",
        "          [var_x_t[..., i], tf.expand_dims(cov_x_log_df[..., i], axis=0)],\n",
        "          axis=0)\n",
        "      vv = 2 * log_df_double_integral[i:i+1] - y_times_g_squared\n",
        "      cov_matrix = tf.concat([cov_matrix, tf.expand_dims(tf.concat([cov_x_log_df[..., i], vv], axis=0),axis=-1)], axis=1)\n",
        "      sigma = tf.math.sqrt(tf.linalg.diag_part(cov_matrix))\n",
        "      sigma_ij = tf.expand_dims(sigma, axis=0) * tf.expand_dims(sigma, axis=1)\n",
        "      corr_matrix = tf.math.divide_no_nan(cov_matrix, sigma_ij)\n",
        "      try:\n",
        "        sqrt_corr = _get_valid_sqrt_matrix(corr_matrix)\n",
        "      except:\n",
        "        sqrt_corr = tf.zeros_like(corr_matrix)\n",
        "      sqrt_cov = tf.math.sqrt(sigma_ij) * sqrt_corr\n",
        "      try:\n",
        "        sqrt_cov = _get_valid_sqrt_matrix(cov_matrix)\n",
        "      except:\n",
        "        sqrt_cov = tf.zeros_like(corr_matrix)\n",
        "      print(times[i], times[i+1], sqrt_cov)\n",
        "      normals = tf.linalg.matvec(sqrt_cov, normals)\n",
        "      normals_x = normals[..., :-1]\n",
        "      normals_df = normals[..., -1]\n",
        "      \n",
        "      vol_x_t = tf.math.sqrt(tf.nn.relu(tf.transpose(var_x_t)[i]))\n",
        "      # If numerically `vol_x_t == 0`, the gradient of `vol_x_t` becomes `NaN`.\n",
        "      # To prevent this, we explicitly set `vol_x_t` to zero tensor at zero\n",
        "      # values so that the gradient is set to zero at this values.\n",
        "      vol_x_t = tf.where(vol_x_t \u003e 0.0, vol_x_t, 0.0)\n",
        "      next_x = (tf.math.exp(-self._mean_reversion * dt[i])\n",
        "                * current_x\n",
        "                + tf.transpose(exp_x_t)[i]\n",
        "                + normals_x)\n",
        "      \n",
        "      # Update `rate_paths`\n",
        "      x_paths = utils.maybe_update_along_axis(\n",
        "          tensor=x_paths,\n",
        "          do_update=keep_mask[i + 1],\n",
        "          ind=written_count,\n",
        "          axis=1,\n",
        "          new_tensor=tf.expand_dims(next_x, axis=1))\n",
        "      \n",
        "      next_log_df = (current_log_df -\n",
        "                     tf.math.reduce_sum(current_x * capital_g, axis=-1) -\n",
        "                     log_df_double_integral[i] + normals_df)\n",
        "      # print(2 * log_df_double_integral[i] - y_times_g_squared, times[i])\n",
        "      log_df_paths = utils.maybe_update_along_axis(\n",
        "          tensor=log_df_paths,\n",
        "          do_update=keep_mask[i + 1],\n",
        "          ind=written_count,\n",
        "          axis=1,\n",
        "          new_tensor=tf.expand_dims(next_log_df, axis=1))\n",
        "\n",
        "      written_count += tf.cast(keep_mask[i + 1], dtype=tf.int32)\n",
        "      return (i + 1, written_count, next_x, next_log_df, x_paths, log_df_paths)\n",
        "\n",
        "    x_paths = tf.zeros(\n",
        "        (num_samples, num_requested_times, self._factors), dtype=self._dtype)\n",
        "    log_df_paths = tf.zeros(\n",
        "        (num_samples, num_requested_times), dtype=self._dtype)\n",
        "    written_count = tf.cast(keep_mask[0], dtype=tf.int32)\n",
        "    initial_x = tf.zeros((num_samples, self._factors), dtype=self._dtype)\n",
        "    initial_log_df = tf.zeros((num_samples,), dtype=self._dtype)\n",
        "    _, _, _, _, x_paths, log_df_paths = tf.while_loop(\n",
        "        cond_fn, body_fn, (0, written_count, initial_x, initial_log_df, x_paths, log_df_paths),\n",
        "        maximum_iterations=steps_num,\n",
        "        swap_memory=True)\n",
        "    \n",
        "    f_0_t = self._instant_forward_rate_fn(times[1:])  # shape=(num_times,)\n",
        "    rate_paths = tf.math.reduce_sum(\n",
        "        x_paths, axis=-1) + f_0_t  # shape=(num_samples, num_times)\n",
        "    \n",
        "    p_0_t = self._initial_discount_rate_fn(times[1:])\n",
        "    discount_factor_paths = tf.math.exp(log_df_paths - p_0_t * times[1:])\n",
        "\n",
        "    return (rate_paths, discount_factor_paths, x_paths)\n",
        "    \n",
        "  def _conditional_mean_x(self, t):\n",
        "    \"\"\"Computes the drift term in [1], Eq. 10.39.\"\"\"\n",
        "    time_index = tf.searchsorted(self._jump_locations, t)\n",
        "    \n",
        "    vn = tf.concat([self._zero_padding, self._jump_locations], axis=0)\n",
        "    zero_padding_2d = tf.zeros((self._factors, self._factors, 1), dtype=self._dtype)\n",
        "    \n",
        "    y_at_vol_knots = tf.concat([zero_padding_2d, self.state_y(self._jump_locations)], axis=-1)\n",
        "    \n",
        "    # Add a training dimension so that we broadcast along `t`\n",
        "    mr = tf.expand_dims(self._mean_reversion, axis=-1)\n",
        "    # create a matrix k2(i,j) = k(i) + k(j)\n",
        "    mr2 = tf.expand_dims(self._mean_reversion, axis=-1)\n",
        "    # Add a dimension corresponding to `num_times`\n",
        "    mr2 = tf.expand_dims(mr2 + tf.transpose(mr2), axis=-1)\n",
        "    ex_between_vol_knots = self._ex_integral(self._padded_knots,\n",
        "                                             self._jump_locations,\n",
        "                                             self._jump_values_vol,\n",
        "                                             mr, mr2,\n",
        "                                             y_at_vol_knots[:, :, :-1])\n",
        "    \n",
        "    ex_at_vol_knots = tf.concat(\n",
        "        [zero_padding_2d,\n",
        "         utils.cumsum_using_matvec(ex_between_vol_knots)], axis=-1)\n",
        "    \n",
        "    c = tf.gather(y_at_vol_knots, time_index, axis=-1)\n",
        "    sigma_t = self._volatility(t)\n",
        "    exp_x_t = self._ex_integral(\n",
        "        tf.gather(vn, time_index, axis=-1), t, sigma_t, mr, mr2, c)\n",
        "    exp_x_t = exp_x_t + tf.gather(ex_at_vol_knots, time_index, axis=-1)\n",
        "    \n",
        "    exp_x_t = tf.math.reduce_sum(exp_x_t, axis=1)\n",
        "    exp_x_t = (exp_x_t[:, 1:] - exp_x_t[:, :-1]) * tf.math.exp(-mr * t[1:])\n",
        "    return exp_x_t\n",
        "\n",
        "  def _ex_integral(self, t0, t, vol, k, k2, y_t0):\n",
        "    \"\"\"Function computes the integral for the drift calculation.\"\"\"\n",
        "    # Computes int_t0^t (exp(k*s)*y(s)) ds,\n",
        "    # where y(s)=y(t0) + int_t0^s exp(-k2*(s-u)) vol(u)^2 du.\"\"\"\n",
        "    vol = tf.expand_dims(vol, axis=-2)\n",
        "    vol_squared = tf.expand_dims(self._rho, axis=-1) * (\n",
        "        vol * tf.transpose(vol, perm=[1, 0, 2]))\n",
        "    value = (\n",
        "        tf.math.exp(k * t) - tf.math.exp(k * t0) + tf.math.exp(k2 * t0) *\n",
        "        (tf.math.exp(-k * t) - tf.math.exp(-k * t0)))\n",
        "    print(t0.shape, t.shape, vol.shape, k.shape, y_t0.shape)\n",
        "    value = value * vol_squared / (k2 * k) + y_t0 * (tf.math.exp(-k * t0) -\n",
        "                                                   tf.math.exp(-k * t)) / k\n",
        "    return value\n",
        "\n",
        "  def _conditional_variance_x(self, t):\n",
        "    \"\"\"Computes the variance of x(t), see [1], Eq. 10.41.\"\"\"\n",
        "    # Add a trailing dimension so that we broadcast along `t`\n",
        "    mr = tf.expand_dims(self._mean_reversion, axis=-1)\n",
        "    # create a matrix k2(i,j) = k(i) + k(j)\n",
        "    mr2 = tf.expand_dims(self._mean_reversion, axis=-1)\n",
        "    # Add a dimension corresponding to `num_times`\n",
        "    mr2 = tf.expand_dims(mr2 + tf.transpose(mr2), axis=-1)\n",
        "\n",
        "    def _integrate_volatility_squared(vol, l_limit, u_limit):\n",
        "      # create sigma2_ij = sigma_i * sigma_j\n",
        "      vol = tf.expand_dims(vol, axis=-2)\n",
        "      vol_squared = tf.expand_dims(self._rho, axis=-1) * (\n",
        "          vol * tf.transpose(vol, perm=[1, 0, 2]))\n",
        "      return vol_squared / mr2 * (tf.math.exp(mr2 * u_limit) - tf.math.exp(\n",
        "          mr2 * l_limit))\n",
        "\n",
        "    var_x_between_vol_knots = _integrate_volatility_squared(\n",
        "        self._jump_values_vol, self._padded_knots, self._jump_locations)\n",
        "    \n",
        "    zero_padding_2d = tf.zeros((self._factors, self._factors, 1), dtype=self._dtype)\n",
        "    varx_at_vol_knots = tf.concat(\n",
        "        [zero_padding_2d,\n",
        "         utils.cumsum_using_matvec(var_x_between_vol_knots)], axis=-1)\n",
        "\n",
        "    time_index = tf.searchsorted(self._jump_locations, t)\n",
        "    vn = tf.concat([self._zero_padding, self._jump_locations], axis=0)\n",
        "\n",
        "    var_x_t = _integrate_volatility_squared(\n",
        "        self._volatility(t),tf.gather(vn, time_index), t)\n",
        "    var_x_t = var_x_t + tf.gather(varx_at_vol_knots, time_index, axis=-1)\n",
        "\n",
        "    var_x_t = (var_x_t[:, :, 1:] - var_x_t[:, :, :-1]) * tf.math.exp(\n",
        "        -mr2 * t[1:])\n",
        "    # return tf.math.reduce_sum(var_x_t, axis=-2)\n",
        "    return var_x_t\n",
        "\n",
        "  def _i_double_integral(self, t0, t, vol, ki, kj, k2, y_t0):\n",
        "    \"\"\"Double integral for discount factor calculations.\"\"\"\n",
        "    # Computes int_t0^t (exp(k*s)*y(s)) ds,\n",
        "    # where y(s)=y(t0) + int_t0^s exp(-k2*(s-u)) vol(u)^2 du.\"\"\"\n",
        "    vol = tf.expand_dims(vol, axis=-2)\n",
        "    vol_squared = tf.expand_dims(self._rho, axis=-1) * (\n",
        "        vol * tf.transpose(vol, perm=[1, 0, 2]))\n",
        "    value = -(1.0 - tf.math.exp(-ki * (t - t0))) / (kj * ki)\n",
        "    value += (1.0 - tf.math.exp(-k2 * (t - t0))) / (kj * k2)\n",
        "    value += (t - t0) / ki\n",
        "    value -= (1.0 - tf.math.exp(-ki * (t - t0))) / (ki * ki)\n",
        "    value = value * vol_squared / k2\n",
        "    #value += y_t0 * ((t - t0) - (1.0 - tf.math.exp(-ki * (t - t0))) / ki) / ki\n",
        "    value2 = tf.math.exp(-kj*t0) * (tf.math.exp(-ki*t0) - tf.math.exp(-ki*t)) / ki\n",
        "    value2 -= (tf.math.exp(-k2*t0) - tf.math.exp(-k2*t)) / k2\n",
        "    value2 = value2 * tf.math.exp(k2*t0) * y_t0 / kj\n",
        "    return value + value2\n",
        "\n",
        "  def _discount_factor_double_integral(self, t):\n",
        "    \"\"\"Computes the double integral in Eq 10.42 and 10.43 in Ref. [1].\"\"\"\n",
        "    knots = tf.sort(tf.concat([t, self._jump_locations], axis=0))\n",
        "    time_index = tf.searchsorted(knots, t)\n",
        "    vn = tf.concat([self._zero_padding, knots], axis=0)\n",
        "    zero_padding_2d = tf.zeros((self._factors, self._factors, 1), dtype=self._dtype)\n",
        "    y_at_knots = tf.concat([zero_padding_2d, self.state_y(knots)], axis=-1)\n",
        "    \n",
        "    mr = tf.expand_dims(self._mean_reversion, axis=-1)\n",
        "    # Add a dimension corresponding to `num_times`\n",
        "    mri = tf.expand_dims(mr, axis=-1)\n",
        "    mrj = tf.expand_dims(tf.transpose(mr), axis=-1)\n",
        "    # create a matrix k2(i,j) = k(i) + k(j)\n",
        "    mr2 = mri + mrj\n",
        "    int_between_knots = self._i_double_integral(\n",
        "        vn[:-1], knots, self._volatility(vn)[:, :-1], mri, mrj, mr2,\n",
        "        y_at_knots[:, :, :-1])\n",
        "    \n",
        "    int_at_knots = utils.cumsum_using_matvec(int_between_knots)\n",
        "    \n",
        "    int_t = tf.gather(int_at_knots, time_index, axis=-1)\n",
        "    int_t = tf.math.reduce_sum(int_t, axis=[0, 1])\n",
        "    print(tf.reduce_sum(int_between_knots, axis=[0, 1]))\n",
        "    return (int_t[1:] - int_t[:-1])\n",
        "\n",
        "  def _cov_between_x_and_log_df(self, t):\n",
        "    \"\"\"Computes covariance between state `x` and log(df) (Eq. )\"\"\"\n",
        "    knots = tf.sort(tf.concat([t, self._jump_locations], axis=0))\n",
        "    time_index = tf.searchsorted(knots, t)\n",
        "    vn = tf.concat([self._zero_padding, knots], axis=0)\n",
        "    zero_padding_2d = tf.zeros((self._factors, self._factors, 1), dtype=self._dtype)\n",
        "\n",
        "    mr = tf.expand_dims(self._mean_reversion, axis=-1)\n",
        "    # Add a dimension corresponding to `num_times`\n",
        "    mri = tf.expand_dims(mr, axis=-1)\n",
        "    mrj = tf.expand_dims(tf.transpose(mr), axis=-1)\n",
        "    # create a matrix k2(i,j) = k(i) + k(j)\n",
        "    mr2 = mri + mrj\n",
        "\n",
        "    def _integrate(vol, t0, t1):\n",
        "      # create sigma2_ij = sigma_i * sigma_j\n",
        "      vol = tf.expand_dims(vol, axis=-2)\n",
        "      vol_squared = tf.expand_dims(self._rho, axis=-1) * (\n",
        "          vol * tf.transpose(vol, perm=[1, 0, 2]))\n",
        "      value = -tf.math.exp(mr2 * t0) * (tf.math.exp(-mrj * t0) - tf.math.exp(-mrj * t1)) / mrj\n",
        "      value += (tf.math.exp(mri * t1) - tf.math.exp(mri * t0)) / mri\n",
        "      value = value * vol_squared * tf.math.exp(-mri * t1) / mr2\n",
        "      return value\n",
        "    \n",
        "    cov_between_knots = _integrate(\n",
        "        self._volatility(vn)[:, :-1], vn[:-1], knots)\n",
        "    \n",
        "    cov_at_knots = utils.cumsum_using_matvec(cov_between_knots)\n",
        "    cov_t = tf.gather(cov_at_knots, time_index, axis=-1)\n",
        "    cov_t = (cov_t[:, :, 1:] - cov_t[:, :, :-1])\n",
        "    return tf.math.reduce_sum(cov_t, axis=-2)\n",
        "\n",
        "  def _exact_discretization_setup(self, dim):\n",
        "    \"\"\"Initial setup for efficient computations.\"\"\"\n",
        "    self._zero_padding = tf.constant([0.0], dtype=self._dtype)\n",
        "    self._jump_locations = tf.sort(tf.reshape(\n",
        "        self._volatility.jump_locations(), [-1]))\n",
        "    self._jump_values_vol = self._volatility(self._jump_locations)\n",
        "    self._padded_knots = tf.concat(\n",
        "        [self._zero_padding, self._jump_locations[:-1]], axis=0)\n",
        "    \n",
        "def _get_valid_sqrt_matrix(rho):\n",
        "  \"\"\"Returns a matrix L such that rho = LL^T.\"\"\"\n",
        "  e, v = tf.linalg.eigh(rho)\n",
        "\n",
        "  def _psd_true():\n",
        "    return tf.linalg.cholesky(rho)\n",
        "\n",
        "  def _psd_false():\n",
        "    print(tf.math.real(e))\n",
        "    realv = tf.math.real(v)\n",
        "    adjusted_e = tf.linalg.diag(tf.maximum(tf.math.real(e), 1e-14))\n",
        "    return tf.matmul(realv, tf.math.sqrt(adjusted_e))\n",
        "\n",
        "  return tf.cond(\n",
        "      tf.math.reduce_any(tf.less(tf.math.real(e), 1e-14)), _psd_false, _psd_true)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XwpkemFuA-J5"
      },
      "source": [
        "# model definition"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gv9Gh1CSy3LQ"
      },
      "outputs": [],
      "source": [
        "dtype = np.float64\n",
        "rate_fn = lambda x: 0.01 * tf.ones_like(x, dtype=dtype)\n",
        "fn = piecewise.PiecewiseConstantFunc([[0.5, 1.0]], [[0.01, 0.02, 0.01]], dtype=dtype)\n",
        "fn2 = piecewise.PiecewiseConstantFunc(\n",
        "    [[0.5, 2.0], [0.5, 2.0]], [[0.005, 0.01, 0.015], [0.005, 0.01, 0.015]], dtype=dtype)\n",
        "fn3 = piecewise.PiecewiseConstantFunc(\n",
        "    [[0.5, 1.0], [0.5, 1.0]], [[0.01, 0.008, 0.005], [0.005, 0.008, 0.005]], dtype=dtype)\n",
        "\n",
        "def vol_fn(t, r):\n",
        "  return fn([t]) * tf.math.abs(r)**0.5\n",
        "\n",
        "model = GaussianHJM(dim=2,\n",
        "                    mean_reversion=[0.5, 0.5],\n",
        "                    volatility=[0.01, 0.01],\n",
        "                    initial_discount_rate_fn=rate_fn,\n",
        "                    corr_matrix=[[1.0, 0.5], [0.5, 1.0]],\n",
        "                    dtype=dtype\n",
        "                    )\n",
        "\n",
        "model2 = GaussianHJM(dim=2,\n",
        "                    mean_reversion=[0.15, 0.03],\n",
        "                    volatility=[0.015, 0.01],\n",
        "                    initial_discount_rate_fn=rate_fn,\n",
        "                    # corr_matrix=[[1.0, 0.5], [0.5, 1.0]],\n",
        "                    dtype=dtype\n",
        "                    )\n",
        "\n",
        "model1 = GaussianHJM(dim=1,\n",
        "                    mean_reversion=[0.03],\n",
        "                    volatility=fn,\n",
        "                    initial_discount_rate_fn=rate_fn,\n",
        "                    corr_matrix=None,\n",
        "                    dtype=dtype\n",
        "                    )"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Xa3Z-pQ3obPa"
      },
      "source": [
        "### Test E(x)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xHdGHvbF406P"
      },
      "source": [
        "### Test double integral"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 21384,
          "status": "ok",
          "timestamp": 1627045804638,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "U84TQ_YwIZV4",
        "outputId": "533c0c2a-01ea-4c77-cd53-6ffbaa1b22a9"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "0 1 (3.948118957575222e-05, 4.383292570659083e-19)\n",
            "1 2 (0.0001088700838819335, 1.208700738185947e-18)\n",
            "2 3 (0.00013439683154279575, 1.4921045681548035e-18)\n",
            "3 4 (0.00014378759720719817, 1.5963630107495926e-18)\n",
            "4 5 (0.00014724226683199051, 1.6347175483487687e-18)\n",
            "5 6 (0.00014851316876299107, 1.6488273942071425e-18)\n",
            "6 7 (0.00014898070745515126, 1.6540181164165364e-18)\n",
            "7 8 (0.00014915270532794917, 1.6559276764022043e-18)\n",
            "8 9 (0.00014921597980927675, 1.6566301642626151e-18)\n",
            "9 10 (0.00014923925719010795, 1.6568885951041329e-18)\n"
          ]
        }
      ],
      "source": [
        "from scipy.integrate import quad\n",
        "\n",
        "k = 0.015\n",
        "v = 0.01\n",
        "def _intfun1(x, t0):\n",
        "  xt = tf.convert_to_tensor([x], dtype=dtype)\n",
        "  return np.exp(k*x) * tf.reduce_sum(model.state_y(xt)).numpy()\n",
        "  yt0 = tf.math.reduce_sum(model.state_y([t0])).numpy()\n",
        "  ff = lambda u: 2*np.exp(-2*k*(x - u))*v**2\n",
        "  aa, _ = quad(ff, t0, x)\n",
        "  return np.exp(k*x) *(yt0 + aa)\n",
        "\n",
        "def _intfun2(x, t0):\n",
        "  val = quad(_intfun1, t0, x, args=(t0))\n",
        "  return np.exp(-k*x) * val[0]\n",
        "\n",
        "for l in range(0, 10):\n",
        "  u = l + 1\n",
        "  print(l,u, quad(_intfun2, l, u, args=(l)))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 725,
          "status": "ok",
          "timestamp": 1627045805342,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "xEO6IaKiofEP",
        "outputId": "9484d0b5-e68a-4872-e509-3bc6af6f2283"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "0 1 (5.48858072538982e-05, 6.093548693841622e-19)\n",
            "1 2 (0.00011430185068066105, 1.2690054638293652e-18)\n",
            "2 3 (0.00013615979153311673, 1.5116773558822763e-18)\n",
            "3 4 (0.00014420087859907658, 1.6009513559187182e-18)\n",
            "4 5 (0.0001471590292153128, 1.6337934251632635e-18)\n",
            "5 6 (0.0001482472720109147, 1.6458753472438602e-18)\n",
            "6 7 (0.00014864761416241959, 1.6503200379871472e-18)\n",
            "7 8 (0.0001487948918093926, 1.651955148333968e-18)\n",
            "8 9 (0.00014884907222785805, 1.6525566718146095e-18)\n",
            "9 10 (0.00014886900408992554, 1.6527779599365194e-18)\n",
            "(1,) (0,) (2, 1, 0) (2, 1) (2, 2, 0)\n",
            "(11,) (11,) (2, 1, 11) (2, 1) (2, 2, 11)\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "\u003ctf.Tensor: shape=(2, 10), dtype=float64, numpy=\n",
              "array([[4.64454365e-05, 9.17023390e-05, 1.08351423e-04, 1.14476279e-04,\n",
              "        1.16729487e-04, 1.17558396e-04, 1.17863335e-04, 1.17975516e-04,\n",
              "        1.18016785e-04, 1.18031967e-04],\n",
              "       [4.64454365e-05, 9.17023390e-05, 1.08351423e-04, 1.14476279e-04,\n",
              "        1.16729487e-04, 1.17558396e-04, 1.17863335e-04, 1.17975516e-04,\n",
              "        1.18016785e-04, 1.18031967e-04]])\u003e"
            ]
          },
          "execution_count": 6,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from scipy.integrate import quad\n",
        "\n",
        "k = 0.015\n",
        "v = 0.01\n",
        "def _intfun1(x, t0, t):\n",
        "  xt = tf.convert_to_tensor([x], dtype=dtype)\n",
        "  return np.exp(-k*t) * np.exp(k*x) * tf.reduce_sum(model.state_y(xt), axis=[1, 2]).numpy()[0]\n",
        "\n",
        "for l in range(0, 10):\n",
        "  u = l + 1\n",
        "  print(l,u, quad(_intfun1, l, u, args=(l, u)))\n",
        "\n",
        "tt = tf.convert_to_tensor(np.arange(0, 11), dtype=dtype)\n",
        "model._conditional_mean_x(tt)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vWSTtAG449BB"
      },
      "source": [
        "### Test covariance"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7MBD5QJR5BO3"
      },
      "outputs": [],
      "source": [
        "from scipy.integrate import quad\n",
        "\n",
        "k = 0.015\n",
        "v = 0.01\n",
        "def _intfun1(x, t0):\n",
        "  return np.exp(2*k*x)\n",
        "\n",
        "def _intfun2(x, t0, t):\n",
        "  val = quad(_intfun1, t0, x, args=(t0))\n",
        "  return np.exp(-k*x) * val[0] * v**2 * np.exp(-k*t)\n",
        "\n",
        "for l in range(0, 10):\n",
        "  u = l + 1\n",
        "  print(l, u, quad(_intfun2, l, u, args=(l, u)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "S0VSh6scPngq"
      },
      "source": [
        "## Test rate sims"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TXJUsKTy5W_O"
      },
      "outputs": [],
      "source": [
        "dt_0 = 0.05\n",
        "times_0 = np.arange(dt_0, 10 + dt_0, dt_0, dtype=np.float64)# np.array([0., 0.5, 1.0, 10])\n",
        "curve_times = np.array([0., 0.5, 1.0, 5.0, 10.0])\n",
        "num_samples = 100000\n",
        "rpaths_0, paths_0, xn_0, _ = model.sample_paths(times=times_0,\n",
        "                           num_samples=num_samples,\n",
        "                           random_type=random.RandomType.STATELESS_ANTITHETIC,\n",
        "                           seed=[0,0],\n",
        "                           time_step=dt_0,\n",
        "                           use_euler_sampling=True)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jpnv5weKO3KS"
      },
      "outputs": [],
      "source": [
        "dt = 1.0\n",
        "times = np.arange(dt, 10 + dt, dt, dtype=np.float64)# np.array([0., 0.5, 1.0, 10])\n",
        "curve_times = np.array([0., 0.5, 1.0, 5.0, 10.0])\n",
        "num_samples = 100000\n",
        "rpaths, paths, xn, _ = model.sample_paths(times=times,\n",
        "                           num_samples=num_samples,\n",
        "                           random_type=random.RandomType.STATELESS_ANTITHETIC,\n",
        "                           seed=[0,0],\n",
        "                           time_step=0.1,\n",
        "                           use_euler_sampling=False)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 6,
          "status": "ok",
          "timestamp": 1627045817209,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "HqWgEQ-GCG2c",
        "outputId": "ae6f341e-a566-43cb-e5f2-268f74dafe4e"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "\u003ctf.Tensor: shape=(2, 9), dtype=float64, numpy=\n",
              "array([[4.64454365e-05, 4.64454365e-05, 4.64454365e-05, 4.64454365e-05,\n",
              "        4.64454365e-05, 4.64454365e-05, 4.64454365e-05, 4.64454365e-05,\n",
              "        4.64454365e-05],\n",
              "       [4.64454365e-05, 4.64454365e-05, 4.64454365e-05, 4.64454365e-05,\n",
              "        4.64454365e-05, 4.64454365e-05, 4.64454365e-05, 4.64454365e-05,\n",
              "        4.64454365e-05]])\u003e"
            ]
          },
          "execution_count": 11,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "t = tf.convert_to_tensor([0, 5, 10], dtype=dtype)\n",
        "# model._conditional_variance_x(t)\n",
        "# model._discount_factor_double_integral(times)\n",
        "model._cov_between_x_and_log_df(times)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 400,
          "status": "ok",
          "timestamp": 1627045818183,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "5gDl_96JBqVL",
        "outputId": "6e703486-c59f-4e61-ddc4-c40972dfbb9a"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "(TensorShape([100000, 10]),\n",
              " TensorShape([100000, 200]),\n",
              " TensorShape([100000, 10]))"
            ]
          },
          "execution_count": 12,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "paths.shape, rpaths_0.shape, rpaths.shape"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 265
        },
        "executionInfo": {
          "elapsed": 390,
          "status": "ok",
          "timestamp": 1627045818895,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "dN0GPjQX0qOc",
        "outputId": "6e572b0c-8d58-4ca0-af8e-e6f1979c035b"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "\u003cFigure size 432x288 with 1 Axes\u003e"
            ]
          },
          "metadata": {
            "needs_background": "light",
            "tags": []
          },
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "dt = np.concatenate([[0], times_0[1:] - times_0[:-1]], axis=0)\n",
        "p_sim = np.mean(np.exp(-np.cumsum(rpaths_0 * dt, axis=1)), axis=0)\n",
        "p_sim_df = tf.math.reduce_mean(paths, axis=0)\n",
        "p_true = np.exp(-times * rate_fn(times))\n",
        "plt.plot(times_0, p_sim, label='E(e(-rdt))')\n",
        "plt.plot(times, p_sim_df, label='E(df)')\n",
        "plt.plot(times, p_true, label='P(0,t)')\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 4,
          "status": "ok",
          "timestamp": 1627045819363,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "preJq3TkBOwK",
        "outputId": "2f2beed0-b710-4d5d-9efd-e03107123fbd"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "(array([0.99004983, 0.98019867, 0.97044553, 0.96078944, 0.95122942,\n",
              "        0.94176453, 0.93239382, 0.92311635, 0.91393119, 0.90483742]),\n",
              " \u003ctf.Tensor: shape=(10,), dtype=float64, numpy=\n",
              " array([0.99004954, 0.98019566, 0.97043916, 0.96078092, 0.95122197,\n",
              "        0.94176246, 0.93239731, 0.92312755, 0.91395011, 0.9048613 ])\u003e)"
            ]
          },
          "execution_count": 14,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "p_true, p_sim_df"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 265
        },
        "executionInfo": {
          "elapsed": 747,
          "status": "ok",
          "timestamp": 1627045821215,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "o7CZgbE1u6fD",
        "outputId": "feb0af98-79e8-4397-bef2-bbe192a3b809"
      },
      "outputs": [
        {
          "data": {
            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD4CAYAAADiry33AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3deVyU5f7/8deHHVxAwSXFrTTN9JiKS4tWVman0mxzSSvzZLac6hy/7ZtlndI8qUdTM/clzS3FDXLP3AFx31ARcQMBEVBkmev3x6A/NFTUgRtmPs/H4zyamfuamfdw8D0311xz32KMQSmllPNyszqAUkqpoqVFr5RSTk6LXimlnJwWvVJKOTkteqWUcnIeVge4XFBQkKldu7bVMZRSqlSJjIw8ZYypVNC2Elf0tWvXJiIiwuoYSilVqojI4Stt06kbpZRyclr0Sinl5LTolVLKyZW4OfqCZGdnEx8fT2ZmptVRnJqPjw/BwcF4enpaHUUp5UCloujj4+MpV64ctWvXRkSsjuOUjDEkJSURHx9PnTp1rI6jlHKgUjF1k5mZSWBgoJZ8ERIRAgMD9a8mpZxQqSh6QEu+GOjPWCnnVGqKXimlnNWZzGymro8lPPqKS+FvSqmYo1dKKWdjjCHycApzNuzDfdccuvA7yRWbwl0THf5cukd/E4YOHcrZs2cL3DZx4kTeeuutS8ZOnjy5UI9ZmHGX69+/P4MHD7743MeOHbu4rWvXruzfv//i9YcffpiUlJTrfg6l1M1LPZvN+D8P8Y/B09g+ti8f7e7M125jqBvoTdv7Hy6S59SivwlXK/r8cnJyGD9+PN27d3fIOGMMNpvtitsvL/rXX3+dQYMGXbzes2dPRo4cec3cSinHMMYQEZvMezM288W3A7gjvDvjMt7kRa8V+N35d+gVhu/bG5GmPYrk+Uvd1M2XC3ay69gZhz5mw2rl+eLJO686JiMjg+eff574+Hhyc3N57rnnOHbsGA8++CBBQUGsXLmSCRMm8O233xIQEECTJk3w9vYGYMWKFTRr1gwPD/uP+8CBA7z55pskJibi5+fHzz//TIMGDf4yLr/Y2FgeffRRWrVqRWRkJIsXL2bq1KlMmjSJypUrU6NGDZo3b87s2bOJiIjghRdewNfXl/Xr19OmTRtefvllcnJy8PDwoGPHjrRp04ZPPvnEoT9HpdSlUs9mM3dLPMs2RNI6ZQEfuK8kyD2VrHI1oFV/3O/qAWULPA6ZQ5W6ordKWFgY1apVY9GiRQCkpqYyYcIEVq5cSVBQEMePH+eLL74gMjISf39/HnzwQZo2bQrA2rVrad68+cXH6tOnD6NHj6ZevXps3LiRN954gxUrVvxl3OX279/PpEmTaN26NZGRkcyYMYPo6GhycnJo1qwZzZs359lnn2XEiBEMHjyYkJCQi/etW7cuW7dupXnz5lSoUIHz58+TlJREYGBgEf3ElHJNF+bep2+MJXVHOF1YymT3Lbh5GHLrtodWr+J120PgVnwTKqWu6K+1511UGjduTL9+/fjggw944oknaNOmzSXbN27cyAMPPEClSvZ35y5durBv3z4Ajh8/zh133AFAeno669at47nnnrt43/Pnz/9lXEFq1apF69atAVizZg2dO3fGz88PgI4dO141f+XKlTl27NjFN5IL17XolXKMC3vvizbsoHnyIt71WEEN95Pk+AbhHvIvaP4yHgE1LclWqKIXkQ7AMMAdGGuM+e6y7d7AZKA5kAR0McbEiognMBZolvdck40x3zowf7G5/fbbiYqKYvHixXz66ac89NBDhb6vr6/vxS8i2Ww2AgICiI6Ovuq4I0eO8OSTTwLQt29fOnToQJkyZW44f2ZmJr6+vle8rpS6fsYYouJSmLbhMMe2r6aL/M509414euaQW+MeaPUtHg2eBA8vS3Ne828HEXEHfgQeAxoC3USk4WXDegMpxpi6wBBgYN7tzwHexpjG2N8EXhOR2o6JXryOHTuGn58fPXr04L333iMqKopy5cqRlpYGQKtWrVi9ejVJSUlkZ2cza9asi/e94447iImJAaB8+fLUqVPn4nZjDFu3bv3LuBo1ahAdHU10dDR9+/b9S562bdsyb948zp07R1paGgsWLLi4LX+uC/bt20ejRo0uPueJEyfQE7wodWPSMrOZtC6WzkPCmTtmAK/teokZHl/Q0Wcbni1fgTc24N57CTR6xvKSh8Lt0bcEYowxBwFEZAbQCdiVb0wnoH/e5dnACLF/zdIAZUTEA/AFsgDHfpJaTLZv3857772Hm5sbnp6ejBo1ivXr19OhQweqVavGypUr6d+/P3fffTcBAQHcddddF+/72GOP0bNnz4vXp02bxuuvv87XX39NdnY2Xbt2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\n",
            "text/plain": [
              "\u003cFigure size 432x288 with 1 Axes\u003e"
            ]
          },
          "metadata": {
            "needs_background": "light",
            "tags": []
          },
          "output_type": "display_data"
        }
      ],
      "source": [
        "dt = np.concatenate([[0], times_0[1:] - times_0[:-1]], axis=0)\n",
        "p_sim = np.std(np.exp(-np.cumsum(rpaths_0*dt, axis=1)), axis=0)\n",
        "p_sim_df = tf.math.reduce_std(paths, axis=0)\n",
        "plt.plot(times_0, p_sim, label='std(e(-rdt))')\n",
        "plt.plot(times, p_sim_df, label='std(df)')\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 335
        },
        "executionInfo": {
          "elapsed": 422,
          "status": "ok",
          "timestamp": 1627045821634,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "naLofes0BaEk",
        "outputId": "133b6925-9bf6-43d4-8d08-7d955ba366e0"
      },
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "/usr/local/lib/python3.7/dist-packages/numpy/lib/function_base.py:2559: RuntimeWarning: invalid value encountered in true_divide\n",
            "  c /= stddev[:, None]\n",
            "/usr/local/lib/python3.7/dist-packages/numpy/lib/function_base.py:2560: RuntimeWarning: invalid value encountered in true_divide\n",
            "  c /= stddev[None, :]\n"
          ]
        },
        {
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "\u003cFigure size 432x288 with 1 Axes\u003e"
            ]
          },
          "metadata": {
            "needs_background": "light",
            "tags": []
          },
          "output_type": "display_data"
        }
      ],
      "source": [
        "dt = np.concatenate([[0], times[1:] - times[:-1]], axis=0)\n",
        "rdf = -np.cumsum(rpaths*dt, axis=1)\n",
        "df = np.log(paths)\n",
        "p_sim, p_sim_df = (np.zeros((df.shape[1])), np.zeros((df.shape[1])))\n",
        "for i, _ in enumerate(p_sim):\n",
        "  p_sim[i] = np.corrcoef(rdf[:, i], xn[:, i, 1])[0][1]\n",
        "  p_sim_df[i] = np.corrcoef(df[:, i], xn[:, i, 1])[0][1]\n",
        "plt.plot(times, p_sim, label='std(e(-rdt))')\n",
        "plt.plot(times, p_sim_df, label='std(df)')\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 335
        },
        "executionInfo": {
          "elapsed": 338,
          "status": "ok",
          "timestamp": 1627045823409,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "3LTSM8-n9B0D",
        "outputId": "c24b2c6f-a40c-4865-f452-53d8edf331f3"
      },
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "/usr/local/lib/python3.7/dist-packages/numpy/lib/function_base.py:2559: RuntimeWarning: invalid value encountered in true_divide\n",
            "  c /= stddev[:, None]\n",
            "/usr/local/lib/python3.7/dist-packages/numpy/lib/function_base.py:2560: RuntimeWarning: invalid value encountered in true_divide\n",
            "  c /= stddev[None, :]\n"
          ]
        },
        {
          "data": {
            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXwAAAD6CAYAAACiefy7AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3dd3xUVfrH8c8JSYBAIIRASEhCQicQQgm9CAisIoIoTRFpinXV1XXFsj91bci6iivrrqAUFbEgCiggVaVDAqGGTkgvJKSQnsz5/XFHFAx1JrnJzPN+vXwxk7mZ++ws+XJy7rnPUVprhBBCOD4XswsQQghROSTwhRDCSUjgCyGEk5DAF0IIJyGBL4QQTkICXwghnIRNga+U8lZKrVNKHbf+2aCcY5oppfYopaKVUoeUUg/Zck4hhBA3RtmyDl8pNQvI1FrPVErNABporZ+95Bh363mKlFJ1gYNAb6110pXe28fHRwcHB99wbUII4YyioqLOaq0blfeaq43vPRIYYH28CPgJuCjwtdbFv3tak2v8rSI4OJjIyEgbyxNCCOeilDpzuddsncP31VonWx+nAL6XKSBQKbUfiAfeutzoXik1XSkVqZSKTE9Pt7E0IYQQv3fVEb5Saj3QpJyXXvj9E621VkqVOz+ktY4HOiql/IHvlFJLtdap5Rw3F5gLEBERIT0fhBDCjq4a+FrrwZd7TSmVqpTy01onK6X8gLSrvFeSUuog0A9Yet3VCiGEuGG2zuGvACYBM61/Lr/0AKVUAJChtS6wruLpC7x7IycrKSkhISGBwsJCG0oWV1OrVi0CAgJwc3MzuxQhhB3ZGvgzga+UUtOAM8BYAKVUBPCQ1vp+oB3wL+t0jwLe1lofuJGTJSQk4OnpSXBwMEopG0sX5dFak5GRQUJCAiEhIWaXI4SwI5sCX2udAdxcztcjgfutj9cBHW05z68KCwsl7CuYUoqGDRsiF82FcDzV7k5bCfuKJ5+xEI7J1ikdIYQQ9qA1nD0GZ7YazyOm2v0UEvhCCGEGSxmkHoIz24yQP7MN8s8arwV0q5DAr3ZTOlXR7Nmzyc/PL/e1hQsX8thjj1107CeffHJN73ktx13q5Zdf5u23375w7qSk3+5xGz9+PMePH7/wfPDgwZw7d+66zyGEuAFlJZAQCVvfg8/HwawQ+LAfrHkWkqKh5WAY8T78eQ9MW1chJcgI3w5mz57Nvffei4eHxxWPKy0tZf78+ezZs8cux2mt0Vrj4lL+v9sLFy6kQ4cO+Pv7A/Dwww8za9Ys5s2bB8DEiRP54IMPeOGFF8r9fiGEDUoKIWkPxG41RvDxu6Akz3itYUsIHQnN+kKzXuAVVCklVdvAf2XlIQ4n5dj1PUP96/HS7e2veExeXh5jx44lISGBsrIyxowZQ1JSEgMHDsTHx4dNmzaxYMEC3nzzTby8vAgPD6dmzZoAbNy4kS5duuDqanzsJ0+e5NFHHyU9PR0PDw/mzZtH27Zt/3Dc78XGxvKnP/2JHj16EBUVxapVq/jss89YtGgRjRs3JjAwkK5du7J06VIiIyOZMGECtWvXZvv27fTr14/JkydTWlqKq6srI0aMoF+/fhL4QthDcZ4R6r9O0SREQlmR8Vrj9tDpHmjWG5r1Ac9yu9BUuGob+GZZs2YN/v7+/PDDDwBkZ2ezYMECNm3ahI+PD8nJybz00ktERUVRv359Bg4cSOfOnQHYunUrXbt2vfBe06dP53//+x+tWrVi586dPPLII2zcuPEPx13q+PHjLFq0iJ49exIVFcUXX3xBdHQ0paWldOnSha5duzJ69GjmzJnD22+/TURExIXvbdmyJfv27aNr1640aNCAoqIiMjIyaNiwYQV9YkI4qIIsiN/52/x70l6wlIJyAb9w6P6AEfBBvcDD2+xqgWoc+FcbiVeUsLAwnn76aZ599lmGDx9Ov379Lnp9586dDBgwgEaNjO6k48aN49ixYwAkJyfTrl07AM6fP8+2bdsYM2bMhe8tKir6w3HladasGT179gRg8+bNjBo16sJ00ogRI65Yf+PGjUlKSrrwD8qvzyXwhbiKvLPW0bt1BJ9yANDg4gZNu0Lvx43Re2B3qFXP7GrLVW0D3yytW7dmz549rFq1ihdffJGbb/7DfWeXVbt27QttISwWC15eXkRHR1/xuPj4eG6//XYAHnroIW655Rbq1Klzw/UXFhZSu3btyz4XQljlJFtH79YRfPoR4+uutSGwGwyYYYzgm0aA+5Wv31UVEvjXKSkpCW9vb+699168vLz46KOP8PT0JDc3Fx8fH3r06METTzxBRkYG9erV4+uvvyY8PByAdu3aceLECQDq1atHSEgIX3/9NWPGjEFrzf79+wkPD7/ouMDAwIv+UYiNjb2onv79+zN58mSee+45SktLWblyJQ8++CDAhbp+79ixY3To0AEwLvqmpKQgG80IgRHwpzb9dpH13Gnj6+6eENQTOo4zRvD+ncHV3dxab5AE/nU6cOAAzzzzDC4uLri5ufHf//6X7du3c8stt+Dv78+mTZt4+eWX6dWrF15eXnTq1OnC9956661MnDjxwvPFixfz8MMP89prr1FSUsL48eMJDw//w3FX0qVLF8aNG0d4eDiNGzemW7duF16bPHkyDz300IWLtjk5OdSuXZsmTYxu11FRUfTs2bPci8NCOIX0Y3DkezjyAyRaN1yq3QCCev82B+8bBjUc42fEpi0OK1JERIS+dMermJiYK85tVwejRo1i1qxZtGrVyi7HXY93332XevXqMW3aNACeeOIJRowYUe60lCN81kL8gcViLJU88j3EfA8Z1vtS/DtD29ug9a3QOBQus9S5OlBKRWmtI8p7zTH+2apGZs6cSXJy8lWD/FqPux5eXl4X/ebQoUOH67oGIUS1VFoMsb8Yo/gjq+B8Cri4QnBf6PEgtLkV6geYXWWlkBG+KJd81qJaK8yBE+uMkD++DopywK0OtBoMbYdDqyHG1I0DkhG+EMLx5abC0R+MkD/1M1hKwMMH2t9hhHzITeBWy+wqTSWBL4Sovs6e+O2ia8JuQEODEGOqpu1wY028Sw2zq6wyJPCFENWHxWLc0fpryJ89anzdrxMMfMG48Nq4HcieDuWSwBdCVG2lxRC72Qj4o6sgNxlUDeOia7f7jYuuXoFmV1ktSODbwezZs5k+fXq53TIXLlxIZGQkc+bMuXCst7c3991330XHxcbGMnz4cA4ePAjA3XffzaFDh5gyZQqJiYkMGzaMQYMGVfz/GCGqgqJc42LrkR/g+FrrRVcPo4Xwrxddq0h/mupEAt8O7N0eOSUlhd27d1+42/bMmTM88MADEvjCseWmwrHV1ouuP0FZMXg0hNARRsg3HwBu0gbEFtU38FfPsDYvsqMmYXDrzCseYs/2yFFRUUydauxqM3To0AvnGDp0KImJiXTq1In333+ffv36kZGRQUpKyoW7ZIVwCOdi4fAKY04+fhfGRddg6D7dmI8P7CEXXe2o+ga+SezZHnnKlCnMmTOH/v3788wzz1z4+ooVKxg+fPhFPXS6dOnC1q1bueuuuyrpf6kQFST9GMQsN4I+Zb/xtSYdYeDz1ouuoXLRtYJU38C/yki8otirPXJWVhZZWVn0798fMHafWr169WXP+2sbYyGqHa2N38ZjVkLMit+6TgZ0gyGvQrvbwTvE3BqdRPUNfJPYqz3y9ZI2xqJa0RoSo+DwciPoz502NgZp1gcipkG74VDP3+wqnY4E/nWyV3tkLy8vvLy82LJlC3379mXx4sVXPO+xY8cu2ixFiCrHUgZxO4xRfMxKyEk0etaE3AR9n4Q2t0HdRmZX6dQk8K+TPdsjL1iwgKlTp6KUuuii7aVKSko4ceLERVsVClEllJUYa+R/vfCalw41akLLm2HQ36HNLQ7bs6Y6kuZplexG2h5/++237Nmzh1dffbUCK7uYI3zWooKUFBobhRxeYdwIVZhlNCZrPdSYj281FGp6ml2l05LmaVXIjbQ9Li0t5emnn67AqoS4iuI840aomBVwbC0U50LN+sZdrqEjoMUgWSNfDdgU+Eopb+BLIBiIBcZqrc9d5th6wGHgO631Yzd6Tq01qhov2WrTpg1t2rS5ru+p7Ln7qvpbn6hkhdlw7EfjwuuJDVBaYNwI1WEUtBsJIf2r7VZ/zsrWEf4MYIPWeqZSaob1+bOXOfZV4BdbTlarVi0yMjJo2LBhtQ79qkxrTUZGBrVqOXcbWaeVl2G0GI5ZCSc3GS2GPf2g873GSD6ot8Ns9+eMbP1/biQwwPp4EfAT5QS+Uqor4AusAW74ymNAQAAJCQmkp6ff6FuIa1CrVi0CApxjByAB5Kb8tkY+divoMvAKMloMh46EphHVess/8RtbA99Xa51sfZyCEeoXUUq5AP8C7gUGX+nNlFLTgekAQUFBf3jdzc2NkBC5QUMImxXnw6FvYe+nxlJKNDRsZSyfbDcC/MLlblcHdNXAV0qtB8pr4PLC759orbVSqrzJ30eAVVrrhKtNw2it5wJzwVilc7XahBDXKf0oRC6AfZ8bc/QNW8GA54zpmkZtJeSriNIyC6417P9b1VUDX2t92VG5UipVKeWntU5WSvkBaeUc1gvop5R6BKgLuCulzmutZ9xw1UKIa1daZEzZRM6HM1vBxc0I+Iipxp2vEvJVwqn086w7nMq6w6l4ebjx0aRudj+HrVM6K4BJwEzrn8svPUBrPeHXx0qpyUCEhL0QlSDzFEQthL2fQX6G0YVy8CvQaYLc8VoFWCya6ISsCyF/Iu08AO396zGoWeMKOaetgT8T+EopNQ04A4wFUEpFAA9pre+38f2FENejrASOrjZG86c2GTtDtbnVGM03HygXX01WWFLG9pMZrD2cyvqYVNJzi6jhoujZ3Jt7ewQxONSXgAZX3lfDFtXqTlshxGVkxcOeT4z/zqdAvabQdTJ0ngj1/Myuzqll55ew8agxiv/5aDp5xWXUca/BgDaNGRLqy8A2janv4Wa388mdtkI4IksZnFhvjOaPrzU6VLYaAhGzoeUQWS9vooRz+RemanaezqTMomnkWZORnZsyJNSX3i0aUtO18jd2kb8RQlQ3uSnGcsqoRZAdD3UaQ9+noMt90KCZ2dU5Ja01h5JyLoT84eQcAFo2rsuD/ZszJNSX8AAvXFzMvUAugS9EdWCxwOmfjdH80VVgKTX2eB36mrFLVA37TQmIa1NSZmHX6cwLIZ+YVYBS0DWoAc8Pa8uQ0CaE+NQxu8yLSOALUZXlnYXoxcZqm8xTUNsbej4MXadAwxZmV+d0zheV8vPRdNYdTmHjkTRyCkup6epCv1Y+PH5zSwa19aWRZ02zy7wsCXwhqhqtIW67MZo/vBzKio0eNgOeN9oPu0mfo8qUllPI+pg01h5OYduJDIrLLDTwcGNIaBOGhPrSv7UPHu7VI0qrR5VCOIOCLNj3hRH0Z48a7Ye7ToGIKdBY9iaoLFprTqafZ+3hVNYeSiU6PguAIG8PJvZqxpBQXyKaNaiQO2ErmgS+EGb6de/XyAVw8BujBXHTrjDyP9D+TnCvuDXZ4mKHkrJZEZ3E2sOpnD6bB0DHgPo8PaQ1Q9r70sbXs9p36ZXAF8IMRblw4GtjNJ9ywNgxKnycMaL373T17xd2kVdUysp9SSzZFce+hGxcXRS9WjRkap9gBof64lffsTZ1kcAXojIVZsOO/8L2D6AoG3w7wG3vQNgYqFXP7OqcxoGEbD7fFceK6ETyisto7VuXl24PZVTnpnh5OO6mLhL4QlSGwhzY+T/YPscI/bbDoc8TENBNmpdVktzCElZYR/MHE3Oo5ebC8I7+3N09iC5BXtV+uuZaSOALUZGKco2g3zbH2Oy7zTAYMMPoNy8qnNaa/QnZLNkVx4p9SeQXl9G2iSf/GNmekZ2aUr+2c92/IIEvREUoOg+75sK296EgE1rfYgS9f2ezK3MKOYUlLI9OYsnOOA4n51DbrQa3h/txd/cgOgU6x2i+PBL4QthTcR7smgfb/m20JG45xNhgJKCr2ZU5PK010fFZLNkVx8p9yRSUlBHqV49X7+jAyE7+1KvlXKP58kjgC2EPxfkQ+TFsfQ/y0qHFzUbQB9p/EwtxseyCEr7bm8iSXXEcScnFw70Gd3Q25ubDmtZ32tF8eSTwhbBFSYGxhn7Lu5CXZvS3GfA8BPUwuzKHprVmT9w5Pt8Zzw8HkigssRDWtD5vjApjRCd/6taUaCuPfCpC3IiSQqO/zZZ3jf7zIf1hwCfQrJfZlTm07PwSlu1NYMmuOI6lnqeOew3u7BLA3d2CCAuob3Z5VZ4EvhDXo7TI2GRk878gNxma9YXRH0NwX7Mrc1haayLPnGPJzjh+OJBMUamF8EAv3rorjOEd/akjo/lrJp+UENeitMjoQb/5HchJNJqZ3TnXGNmLCnEur5hl1rn5E2nn8azpytiIQMZ3D6S9v4zmb4QEvhBXUlpstCf+5W3ISYDAHkafm+YD5IapCqC1ZufpTJbsimP1wRSKSy10DvJi1uiODO/oV226UlZV8ukJUZ6yEoj+3Aj67DjjjtgR/4YWgyToK0BmXjHfRCWwZHccp9Lz8Kzlyj3dgxjfPZC2TaTlhL1I4Avxe2WlsP8L+HkWZJ0B/y4w/B1oOViCvgIcTMxm/tbTfL8vmeIyCxHNGvDomJYMC/Ojtnvl7/nq6CTwhQAj6A98ZQT9udPg1wmG/RNaDZWgt7Myi2bd4RTmb4llV2wmHu41uLt7IBN6NqO1r6fZ5Tk0CXzh3CxlcGAp/PwWZJ6EJh1h/BJoc6sEvZ3lFJbw1e54Fm6LJeFcAQENavPibe0YExHodD1tzCKBL5yTpQwOLjOCPuM4+IbBuMXGhuAS9HZ1+mweC7eeZmlUAnnFZXQP8ebF20IZEupLDRf5rCuTBL5wLhYLHP4WfnrL2EawcXsY+6nRrtil+m1ZV1Vprdl2MoP5W06z8Wgabi4u3B7uz5Q+wXRoKksqzSKBL5xDSaExR79tjhH0jdrBmIXQbqQEvR0VlpTx3d5EFmyN5WhqLj513Xl8UCsm9Ayisadsvm42CXzh2PIzjaZmO+cavW6ahMHo+RA6SoLejlJzCvl0+xkW7zzDufwS2vnV45+jO3J7uD+13GS1TVVhU+ArpbyBL4FgIBYYq7U+V85xZcAB69M4rfUIW84rxFVlnjK2Etz7GZTkG22Ke//ZuDNW5ujtZl98FvO3nuaH/cmUac2Qdr5M7RtCjxBv6VJZBdk6wp8BbNBaz1RKzbA+f7ac4wq01rIzs6h4CZFGL/qYlaBqQMdx0OtR8A01uzKHUVpmYc2hFBZsjSXqzDnq1nRlUu9gJvUKJqihh9nliSuwNfBHAgOsjxcBP1F+4AtRcSwWOLba2F0qbjvUqg99noTu06Gen9nVOYys/GK+2B3PJ9tiScoupFlDD166PZTRXQPwlM1FqgVbA99Xa51sfZwC+F7muFpKqUigFJiptf6uvIOUUtOB6QBBQUE2liYcXkmB0f5g+3+MNfReQXDLW9D5XqhZ1+zqHMaJtPMs3Haab6ISKSgpo1fzhrwysgOD2jaWZZXVzFUDXym1HmhSzksv/P6J1lorpfRl3qaZ1jpRKdUc2KiUOqC1PnnpQVrrucBcgIiIiMu9l3B2eWeNbQR3zzO2EfTvDKMXQLsRUEPWIdiD1ppfjp9l/pbT/HwsHXdXF+7o5M+UPiG085PeNtXVVX86tNaDL/eaUipVKeWntU5WSvkBaZd5j0Trn6eUUj8BnYE/BL4QV3T2BGyfA/uWQGkhtL7VuBDbrLdciLWT/OJSlu1JZMHW05xMz6ORZ02eGtKae3oE4VO3ptnlCRvZOhxaAUwCZlr/XH7pAUqpBkC+1rpIKeUD9AFm2Xhe4Sy0hrgdxvz80VVQwx3Cx0Ovx6BRa7OrcxhJWQV8sv0MS3bFkV1QQoem9Xh3XDi3hfnj7irLVx2FrYE/E/hKKTUNOAOMBVBKRQAPaa3vB9oBHyqlLIALxhz+YRvPKxydpcxYabPtfUiMhNoNoP8z0P0BqNvY7Oocxp64c8zfcprVB1PQWvOn9k2Y2jeEiGYNZFmlA7Ip8LXWGcDN5Xw9Erjf+ngbEGbLeYQTKc6DvYthx3/gXCw0CIFhb0One8C9jtnVOYzDSTnM+vEIPx1Nx7OWK9P6hjCxZzMCvWVZpSOTK1yiashNhV1zYfdHUJhlbDgy5FWjmZmL3KlpL/GZ+byz7hjfRSfiWdOVGbe2ZWLPZrIvrJOQ/5eFudKOGBdi939p7DLV9jbo/TgE9TC7MoeScb6I9zeeYPHOM7goxYP9W/DwTS2o7yHr552JBL6ofFpD7BZjfv74j+BaCzpPNO6IbdjC7OocSl5RKR9tPs28zafILy5lbEQgTwxuhV/92maXJkwggS8qT1kpHP7OCPrkaPDwgQHPQ7dpUMfH7OocSnGphSW74nh/43HOni/mlvZN+Ouf2tCysdyQ5swk8EXFs5TBnk9g878gOx4atoThs43llW4y0rQni0Wzcn8S/1p7jLjMfHqEeDP3vrZ0CWpgdmmiCpDAFxUrbgesegZS9kNAd7h1FrS+RVoT25nWms3Hz/LWmiMcSsqhbRNPFkzpxoDWjWR5pbhAAl9UjJwkWPeSselIvaZGD/r2d8odsRVgX3wWb605wraTGTT1qs2748IZGd4UF+lzIy4hgS/sq7TIaGb2y9tgKTVulur7F1lDXwFOn83j7R+P8sOBZLzruPN/w0OZ0DOImq6yjFWUTwJf2M+xH2HNDGPzkbbDYehr4B1idlUOJy2nkPc2HOeL3fHUdHXh8UEteaB/c2lRLK5KAl/Y7uwJ+PE5OL4WGraCe5dByz/cgC1slFNYwoc/n2T+llhKyixM6BHEnwe1opGnNDUT10YCX9y4olz45Z+w/QNjLf3Q141NR1zdza7MoRSWlPHZjjPM2XSCrPwSbg/35+khrQn2kWkycX0k8MX1s1iMi7Hr/g/Op0Kne+Hm/wPPy+1/I25EmUXz7d5E3l13jMSsAvq18uHZW9rSoWl9s0sT1ZQEvrg+SXth1d8gYRc07QrjP4eACLOrcihaazYeSWPWmqMcTc0lrGl9Zo3uSJ+WcnOasI0Evrg2eWdhwz+MG6jq+MDI/0D4PbKe3s6izmQyc/URdseeI7ihB3Pu6cywDn6yxFLYhQS+uLKyUqOD5aY3oCTP6Hdz09+MjcKF3RxLzWXWmqOsj0nFp25NXrujA+O6BeJWQ/5BFfYjgS8u79TPsPpZSI+B5gPh1regURuzq3IoSVkFvLvuGN/sSaCOuyt/HdqaqX1D8HCXH01hf/K3SvxRVhz8+ALErACvZsY8fZthcpesHWXlF/PBTydZuC0WNEztE8IjA1viXUdWOImKI4EvflNSAFvfgy3vAgoGvmhsEu5Wy+zKHIbWmuXRSby88hDZBSXc2TmAvwxpRUAD2WlKVDwJfGH0p49ZAT++CNlxRs+boa9C/QCzK3MoaTmFPP/tQdbHpNIlyIs37gyjbZN6ZpclnIgEvrNLi4HVf4PTv0Dj9jD5Bwjua3ZVDkVrYz39yysOUVRq4cXb2jGlTwg1ZOWNqGQS+M6qIAt+ehN2zYOansZG4V2nQA35K2FPqTmFPL/sABuOpBHRrAGzRnekeSPZhESYQ366nY2lDPZ+BhtegfxMiJhizNXXaWh2ZQ5Fa803exL5x8pDFJdZ+PvwUCb3DpZRvTCVBL4zid9lbEaSHA1BvYxlln7hZlflcFKyC3lu2X42HU2nW3ADZo0OJ0T63ogqQALfGeSmGJuR7P8CPP3hro+hw12yzNLOtNZ8HZXAq98fpqTMwku3hzKpV7DcJSuqDAl8R1ZSADs+gM3vQFkx9H0K+j0NNWUO2d6SswuY8c0Bfj6WTvcQb2bd1VG6WYoqRwLfEVkscPAbY54+O964aWroa9CwhdmVORytNV9FxvPa9zGUWjSvjGjPxJ7NZFQvqiQJfEdzZjv8+Dwk7YEmHeGODyCkv9lVOaTErAJmfLOfzcfP0iPEm3+ODieoodxAJaouCXxHkXnKmKePWWHM09/xP+g4TrpZVgCtNV/sjuf1H2KwaM2rI9szoYeM6kXVZ1PgK6W8gS+BYCAWGKu1PlfOcUHAR0AgoIFhWutYW84trArOGRuG7/wQarjDwBeg12PgLiPNipBwLp/nlh1g8/Gz9GrekFmjOxLoLZ+1qB5sHeHPADZorWcqpWZYnz9bznGfAK9rrdcppeoCFhvPK0qLIfJj+Pkt4yaqzvfCoBfBs4nZlTkkrTVLdsXzxqoYtNa8dkcH7ukeJKN6Ua3YGvgjgQHWx4uAn7gk8JVSoYCr1nodgNb6vI3ndG5aw5EfYN3fjWmc5gOMvWSbdDC7MocVn2mM6recOEuflg2ZeaeM6kX1ZGvg+2qtk62PU4DyNjVtDWQppZYBIcB6YIbWuuzSA5VS04HpAEFBQTaW5oCS9hpti89sBZ82cM/X0GqIrKevIBaL5vNdcby5KgaA10cZo3oln7eopq4a+Eqp9UB58wQv/P6J1lorpfRlztEP6AzEYcz5TwY+vvRArfVcYC5AREREee/lnLITYMOrxo1THj5w27+gy2Tpe1OB4jPzefab/Ww7mUHflj7MvCtMWhiLau+qiaG1Hny515RSqUopP611slLKD0gr57AEIFprfcr6Pd8BPSkn8MUlinJhy2zYPseYyun7F+M/2V6wwlgsmsU7z/Dm6iO4KMWbd4YxvlugjOqFQ7B1iLgCmATMtP65vJxjdgNeSqlGWut0YBAQaeN5HZulDPZ+Chtfh7w06DAaBr8EXjLNVZHiMvL52zf72HEqk36tfJh5V0eaetU2uywh7MbWwJ8JfKWUmgacAcYCKKUigIe01vdrrcuUUn8FNihjmBQFzLPxvI7rxHpY+3dIOwyBPeHuJRAQYXZVDs1i0Xy64wwzVx/B1UXx1l1hjI2QUb1wPDYFvtY6A7i5nK9HAvf/7vk6oKMt53J4aTGw9kUj8BsEw5hFEDpSLshWsDMZeTyzdD+7TmdyU+tGvHlnGP4yqhcOSq76me18Gmx6HfZ8YmxEMvR16P4AuNY0u/2RgkYAABFLSURBVDKHZrFoFm2PZdaao7jWUMwa3ZExXQNkVC8cmgS+WUoKYPt/jA3DSwuh+3S46Vnw8Da7MocXezaPvy3dz67YTAa2acQbd4bhV19G9cLxSeBXNosFDnwNG/4BOQnQdjgMfgV8WppdmcP7dVT/1pojuNVw4e0x4dzVpamM6oXTkMCvTGe2WTtZ7jV2mrrzQ9kwvJIkZxfw16/3sfVEBoPaNuaNUWE0qV/L7LKEqFQS+JUh4ySs+z848r3RyXLUhxA2VjpZVpIV+5J48dsDlFo0M+8MY5ysqxdOSgK/IuVnwi//hF3zrJ0sX4Rej0ony0qSnV/C35cfZMW+JDoHefHu2E6yC5VwahL4FcFigT2LYP3LUJQDnScabYs9y2s1JCrC1hNn+evX+0jPLeLpIa15eEALXGvIb1TCuUng21taDKx8EuJ3QLO+MGwW+LY3uyqnUVhSxj9/PMrHW07TvFEdlj3Sm44BXmaXJUSVIIFvLyUFxvTN1veM9fQjP4BO98iNU5XoUFI2f/kymmOp55nUqxkzbm1HbfcaZpclRJUhgW8PJzfB93+Bc6ch/B4Y+irU8TG7KqdRZtHM/eUU76w7SgMPdxZN7c5NrRuZXZYQVY4Evi3Op8PaF2D/l+DdAu5bAc1vMrsqpxKfmc/TX+1jV2wmw8Ka8PodYTSo4252WUJUSRL4N0Jr2PuZsetU0Xno/zfo9zS4ybruyqK1ZmlUAq+sPIwC3hkbzqjOchOVEFcigX+90o/B908au04F9YLhs6FxW7OrciqZecU8v+wAaw6l0D3Em3fGhsvmJEJcAwn8a1VSCFvegc3vGOvob/+3sdxSbp6qVJuOpPHM0v3kFJTw/LC2TOvbnBqykbgQ10QC/1qc/sW4KJtxwrhD9k+vQ93GZlflVPKLS3ljVQyf7Yijja8nn07rTju/emaXJUS1IoF/JXkZxjx99GKjR/29y6DlH9r/iwoWHZ/FU19Gczojjwf6hfD00DbUcpPllkJcLwn88mgN+74wGp0V5UDfp+Cmv4GbtNCtTKVlFv6z6ST/3ngcX8+aLL6/B71byHJXIW6UBP6lzp6AH/5iTOME9jAuyvqGml2V0zl9No8nv4xmX3wWozo35eUR7alf283ssoSo1iTwf1VaZNwl+8vb4FoLhr8LXSbLRdlKprXm811xvPZ9DO6uLsy5pzPDO/qbXZYQDkECH4w+9SufhLNHof2dcMub4NnE7KqcTlpuITO+OcDGI2n0a+XDP0eHS896IezIuQM/PxPWv2TsJ1s/CCYshVZDzK7KKf14KIXnlh0gr6iUl28P5b5ewbjIcksh7Mo5A19rY5vBNc9BwTno84Sxn6y79EqvbOeLSvnHykN8FZlAh6b1mD2uEy0be5pdlhAOyfkCP/MUfP8UnNoETbvCfd9BkzCzq3JKu2MzeeqraBLPFfDYwJY8fnMr3F3lmokQFcV5Ar+0GLa/Dz/PAhc3GPY2REwFF1nPXdmKSy3MXn+M//18koAGHnz1YC8igr3NLksIh+ccgR+30+h/k3YY2o2AW9+CerLywwzHU3N58stoDiXlML5bIC8OD6VuTef4ayiE2Rz7J60gCza8ApHzoV4A3P0FtLnV7KqcksWiWbQ9lpmrj1C3pitzJ3ZlaHtZCSVEZXLMwNcaDi2D1TMg/yz0egwGPAc165pdmVMqKC7jz0v2sj4mlZvbNmbmXR1p5FnT7LKEcDo2Bb5Syhv4EggGYoGxWutzlxwzEHj3d19qC4zXWn9ny7kvKzcFlj8GJ9aBXyeY8DX4d6qQU4mry8wrZtqi3UTHZ/F/w0OZ0idYetYLYRJbl0TMADZorVsBG6zPL6K13qS17qS17gQMAvKBtTae9/LcahtbDd4yEx7YKGFvovjMfEb/bxuHknL474QuTO0bImEvhIlsndIZCQywPl4E/AQ8e4XjRwOrtdb5Np738mrVh0d2Qg3HnK2qLg4lZTN5wW6KSsr4bFoPuofIKhwhzGbrCN9Xa51sfZwC+F7l+PHAksu9qJSarpSKVEpFpqen33hVEvam2nriLOM+3IGri2Lpw70l7IWoIq6ajEqp9UB5yyle+P0TrbVWSukrvI8fEAb8eLljtNZzgbkAERERl30vUXUtj07kr1/vo7lPXRZO7YZffWkpLURVcdXA11oPvtxrSqlUpZSf1jrZGuhpV3irscC3WuuSG6hTVAPzfjnF66ti6B7izbz7IqSdsRBVjK1TOiuASdbHk4DlVzj2bq4wnSOqL4tF8+r3h3l9VQy3hfnxydTuEvZCVEG2Bv5MYIhS6jgw2PocpVSEUuqjXw9SSgUDgcDPNp5PVDFFpWU8/sVePt5ymsm9g3n/7s6y/aAQVZRNVze11hnAHzZ51VpHAvf/7nks0NSWc4mqJ6ewhAc/iWL7qQxm3NqWB/s3l2WXQlRhspxF3JDUnEImzd/FibTzvDM2nDu7BJhdkhDiKiTwxXU7kZbLpPm7ycovZv7kbvRv3cjskoQQ10ACX1yXqDOZTF0YiVsNxZcP9qJD0/pmlySEuEYS+OKarT2Uwp+X7MXfqzaLpnQnqKGH2SUJIa6DBL64Jot3nuHv3x0kLMCL+ZMiaFhXul0KUd1I4Isr0lrzzrpjvL/xBAPbNOI/E7rg4S5/bYSojuQnV1xWaZmF5789wFeRCYyNCOCNUWG41pA9Z4WoriTwRbnyi0t5dPEeNh1N5/FBLfnLkNayxl6Iak4CX/xBxvkipi6K5EBCFq+P6sCEHs3MLkkIYQcS+OIicRn5TFqwi6SsAv53r+w7K4QjkcAXFxxMNDYtKbVY+PyBHnRtJn3shXAkEvgCgF+OpfPwZ1F4ebjzxdQetGzsaXZJQgg7k8AXfLs3gWe+3k/LxnVZNLU7vvVqmV2SEKICSOA7Ma01H/5yipmrj9CreUM+vK8r9WpJH3shHJUEvpMqs25asnBbLMM7+vGvseHUdJU+9kI4Mgl8J1RYUsZTX0Wz6kAK0/qG8MKwdri4yBp7IRydBL6TyS4oYfonkew8nckLw9rxQP/mZpckhKgkEvhOJDm7gMnzd3Pq7HneG9+JkZ1kEzIhnIkEvpM4lprLpPm7yC0sZeGU7vRp6WN2SUKISiaB7wR2x2YybeFuarrV4MsHe9LeXzYtEcIZSeA7uHWHU3n08z0ENDA2LQn0lk1LhHBWEvgObNWBZB5fspf2TeuzYHI3vOu4m12SEMJEEvgOanl0Ik99tY/OgV4smNINT7mhSginJ7tZOKClUQk8+WU0Ec0asGhqdwl7IQQgI3yHs2RXHM9/e4A+LXyYd18Etd3l7lkhhEFG+A7kk+2xPLfsADe1bsRHkyTshRAXkxG+g/ho8yle+yGGIaG+zLmns/TFEUL8gQS+A/jgpxPMWnOUYWFNeG98Z9xko3EhRDlsSgallLdSap1S6rj1zwaXOW6WUuqQUipGKfVvJbth24XWmvfWH2fWmqOM7OTPvyXshRBXYGs6zAA2aK1bARuszy+ilOoN9AE6Ah2AbsBNNp7X6WmteXvtUd5df4zRXQN4Z2wnXCXshRBXYGtCjAQWWR8vAu4o5xgN1ALcgZqAG5Bq43mdmtaaN1bF8J9NJ7m7exCz7upIDWlvLIS4ClsD31drnWx9nAL4XnqA1no7sAlItv73o9Y6prw3U0pNV0pFKqUi09PTbSzNMWmteWXlYeZtPs2kXs14Y1QH6WUvhLgmV71oq5RaDzQp56UXfv9Ea62VUrqc728JtAMCrF9ap5Tqp7XefOmxWuu5wFyAiIiIP7yXs7NYNC98d5Alu+K4v28IL9zWDrkcIoS4VlcNfK314Mu9ppRKVUr5aa2TlVJ+QFo5h40Cdmitz1u/ZzXQC/hD4IvLK7Nonv1mP0ujEnhkQAue+VMbCXshxHWxdUpnBTDJ+ngSsLycY+KAm5RSrkopN4wLtuVO6YjylZZZeOqraKNlwuBWEvZCiBtia+DPBIYopY4Dg63PUUpFKKU+sh6zFDgJHAD2Afu01ittPK/TKCmz8MQX0SyPTuKZP7XhycGtJeyFEDfEphuvtNYZwM3lfD0SuN/6uAx40JbzOKui0jL+/Ple1h5O5cXb2nF/P9l/Vghx4+RO2yqqsKSMhz+LYtPRdF4Z0Z5JvYPNLkkIUc1J4FdBBcVlTP80ki0nzvLGqDDu6RFkdklCCAcggV/F5BWVMm3RbnaezmTWXR0ZExFodklCCAchgV+F5BaWMGXBbvbEnWP2uE6M7NTU7JKEEA5EAr+KyC4oYdL8XRxMzOb9u7twW0c/s0sSQjgYCfwq4FxeMRPn7+RoSi4fTOjC0Pbl3dgshBC2kcA3Wcb5IiZ8tJNTZ/OYOzGCgW0bm12SEMJBSeCbKC23kAnzdhJ/Lp+PJ0XQr1Ujs0sSQjgwCXyTpGQXcs+8HaTkFLJgcnd6tWhodklCCAcngW+CxKwC7pm3g4zzxXwytTsRwd5mlySEcAIS+JUsLiOfu+ftIKewhE+ndadzULm7QgohhN1J4Fei02fzuGfeDgpKyljyQE86NK1vdklCCCcigV9JTqTlcve8nZRZNJ/f35NQ/3pmlySEcDIS+JXgSEoOE+btRCnFF9N70trX0+yShBBOSAK/gh1MzGbixztxd3Xh8wd60qJRXbNLEkI4KQn8CrQvPouJH+/Es5Ybnz/Qg2YN65hdkhDCiUngV5C9cee47+NdeNVxY8kDPQlo4GF2SUIIJyeBX0EaedakU5AXs0Z3xK9+bbPLEUIICfyKEtDAg0+n9TC7DCGEuMDWTcyFEEJUExL4QgjhJCTwhRDCSUjgCyGEk5DAF0IIJyGBL4QQTkICXwghnIQEvhBCOAmltTa7hnIppdKBM2bXYSMf4KzZRVQh8nlcTD6P38hncTFbPo9mWutyN8iusoHvCJRSkVrrCLPrqCrk87iYfB6/kc/iYhX1eciUjhBCOAkJfCGEcBIS+BVrrtkFVDHyeVxMPo/fyGdxsQr5PGQOXwghnISM8IUQwklI4AshhJOQwK8ASqlApdQmpdRhpdQhpdQTZtdkNqVUDaXUXqXU92bXYjallJdSaqlS6ohSKkYp1cvsmsyklPqL9efkoFJqiVKqltk1VSal1HylVJpS6uDvvuatlFqnlDpu/bOBPc4lgV8xSoGntdahQE/gUaVUqMk1me0JIMbsIqqI94A1Wuu2QDhO/LkopZoCjwMRWusOQA1gvLlVVbqFwC2XfG0GsEFr3QrYYH1uMwn8CqC1TtZa77E+zsX4gW5qblXmUUoFALcBH5ldi9mUUvWB/sDHAFrrYq11lrlVmc4VqK2UcgU8gCST66lUWutfgMxLvjwSWGR9vAi4wx7nksCvYEqpYKAzsNPcSkw1G/gbYDG7kCogBEgHFlinuD5SStUxuyizaK0TgbeBOCAZyNZarzW3qirBV2udbH2cAvja400l8CuQUqou8A3wpNY6x+x6zKCUGg6kaa2jzK6linAFugD/1Vp3BvKw06/r1ZF1bnokxj+E/kAdpdS95lZVtWhj7bxd1s9L4FcQpZQbRtgv1lovM7seE/UBRiilYoEvgEFKqc/MLclUCUCC1vrX3/iWYvwD4KwGA6e11ula6xJgGdDb5JqqglSllB+A9c80e7ypBH4FUEopjDnaGK31O2bXYyat9XNa6wCtdTDGxbiNWmunHcFprVOAeKVUG+uXbgYOm1iS2eKAnkopD+vPzc048UXs31kBTLI+ngQst8ebSuBXjD7ARIzRbLT1v2FmFyWqjD8Di5VS+4FOwBsm12Ma6286S4E9wAGMTHKqNgtKqSXAdqCNUipBKTUNmAkMUUodx/gtaKZdziWtFYQQwjnICF8IIZyEBL4QQjgJCXwhhHASEvhCCOEkJPCFEMJJSOALIYSTkMAXQggn8f/PyBi3thVJ5QAAAABJRU5ErkJggg==\n",
            "text/plain": [
              "\u003cFigure size 432x288 with 1 Axes\u003e"
            ]
          },
          "metadata": {
            "needs_background": "light",
            "tags": []
          },
          "output_type": "display_data"
        }
      ],
      "source": [
        "dt = np.concatenate([[0], times[1:] - times[:-1]], axis=0)\n",
        "rdf = -np.cumsum(rpaths*dt, axis=1)\n",
        "df = np.log(paths)\n",
        "p_sim, p_sim_df = (np.zeros((df.shape[1])), np.zeros((df.shape[1])))\n",
        "for i, _ in enumerate(p_sim):\n",
        "  p_sim[i] = np.corrcoef(rdf[:, i], xn[:, i, 1])[0][1]\n",
        "  p_sim_df[i] = np.corrcoef(df[:, i], xn[:, i, 1])[0][1]\n",
        "plt.plot(times, p_sim, label='std(e(-rdt))')\n",
        "plt.plot(times, p_sim_df, label='std(df)')\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 181
        },
        "executionInfo": {
          "elapsed": 492,
          "status": "error",
          "timestamp": 1627045823897,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "Tp8Rbqw5vl5I",
        "outputId": "dc0a1a02-9331-4716-d37e-c73c9e9be648"
      },
      "outputs": [
        {
          "ename": "NameError",
          "evalue": "ignored",
          "output_type": "error",
          "traceback": [
            "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
            "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
            "\u001b[0;32m\u003cipython-input-18-1a986adc383e\u003e\u001b[0m in \u001b[0;36m\u003cmodule\u003e\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0midx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m50\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----\u003e 2\u001b[0;31m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcov\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpp\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0midx\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mpp\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0midx\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mxn\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0midx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnumpy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mxn\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0midx\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnumpy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
            "\u001b[0;31mNameError\u001b[0m: name 'pp' is not defined"
          ]
        }
      ],
      "source": [
        "idx = 50\n",
        "np.cov(pp[:, idx] - pp[:, idx-1], xn[:, idx, 0].numpy() - xn[:, idx-1, 0].numpy())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "R-RDKXCLPtPA"
      },
      "source": [
        "## Test Bond sims"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DKMBUg5yfGuw"
      },
      "outputs": [],
      "source": [
        "p_t_tau, _, _ = model.sample_discount_curve_paths(\n",
        "    times=times,\n",
        "    curve_times=curve_times,\n",
        "    num_samples=num_samples,\n",
        "    time_step=0.1)\n",
        "print(p_t_tau.shape)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 349,
          "status": "ok",
          "timestamp": 1627045827831,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "EOO9Ot66SobJ",
        "outputId": "5462345e-eed0-42bd-90ae-ac56c13aa2e1"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "TensorShape([100000, 5, 10])"
            ]
          },
          "execution_count": 20,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "p_t_tau.shape"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 299
        },
        "executionInfo": {
          "elapsed": 709,
          "status": "ok",
          "timestamp": 1627045828941,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "-W7ke1gWD1Wr",
        "outputId": "ef2c9048-2798-45d7-e852-a946e942f72c"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[0.         0.00935858 0.01857783 0.08755848 0.16292077]\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "[\u003cmatplotlib.lines.Line2D at 0x7f63d1f5d9d0\u003e]"
            ]
          },
          "execution_count": 21,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        },
        {
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "\u003cFigure size 432x288 with 1 Axes\u003e"
            ]
          },
          "metadata": {
            "needs_background": "light",
            "tags": []
          },
          "output_type": "display_data"
        }
      ],
      "source": [
        "def bond_std(t, T, v, k):\n",
        "  eT = np.exp(-k*T)\n",
        "  et = np.exp(k*t)\n",
        "  #return np.sqrt(v**2/k**2 *(t-eT**2*(et**2-1.)/2/k+2*eT*(et - 1)/k))\n",
        "  val = v/k * (1. - eT*et) * np.sqrt((1.-1./et/et)/k/2)\n",
        "  print (val)\n",
        "  return val\n",
        "d = tf.math.reduce_std(tf.math.log(p_t_tau), axis=0)\n",
        "tidx = 3\n",
        "plt.plot(d[:,tidx])\n",
        "plt.plot(bond_std(times[tidx], curve_times + times[tidx], 0.01, 0.03))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 299
        },
        "executionInfo": {
          "elapsed": 369,
          "status": "ok",
          "timestamp": 1627045830306,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "nQTiPi1qNYFm",
        "outputId": "e005fa32-747e-4cfb-be12-84af7a8676b7"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[0.         0.00933947 0.01853989 0.08737965 0.16258801]\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "[\u003cmatplotlib.lines.Line2D at 0x7f63d1edfd50\u003e]"
            ]
          },
          "execution_count": 22,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        },
        {
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "\u003cFigure size 432x288 with 1 Axes\u003e"
            ]
          },
          "metadata": {
            "needs_background": "light",
            "tags": []
          },
          "output_type": "display_data"
        }
      ],
      "source": [
        "d = tf.math.reduce_std(tf.math.log(p_t_tau), axis=0)\n",
        "tidx = 2\n",
        "plt.plot(d[:,tidx])\n",
        "plt.plot(bond_std(times[tidx], curve_times + times[tidx],\n",
        "                  np.sqrt(0.005**2+0.008**2+2*0.5*0.005*0.008), 0.03))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 7,
          "status": "ok",
          "timestamp": 1627045831621,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "-tkhyYgpYH2U",
        "outputId": "0585cee2-e771-408f-cc74-557ada0820b0"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "\u003ctf.Tensor: shape=(3,), dtype=int32, numpy=array([3, 2, 1], dtype=int32)\u003e"
            ]
          },
          "execution_count": 23,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "aa = tf.convert_to_tensor([1,2,3])\n",
        "tf.reverse(tf.expand_dims(aa, axis=0), [1])[0]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uPpqXD-24UNX"
      },
      "source": [
        "## Discount bond price"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 6,
          "status": "ok",
          "timestamp": 1627045832990,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "LbERcB4121K7",
        "outputId": "536def26-8dc3-4e3f-d523-db58662af408"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "(\u003ctf.Tensor: shape=(2, 2, 1), dtype=float64, numpy=\n",
              " array([[[9.93262053e-05],\n",
              "         [4.96631027e-05]],\n",
              " \n",
              "        [[4.96631027e-05],\n",
              "         [9.93262053e-05]]])\u003e,\n",
              " \u003ctf.Tensor: shape=(2, 2, 1), dtype=float64, numpy=\n",
              " array([[[0.00058265],\n",
              "         [0.        ]],\n",
              " \n",
              "        [[0.        ],\n",
              "         [0.00043197]]])\u003e)"
            ]
          },
          "execution_count": 24,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "model.state_y([5.0]), model2.state_y([5.0])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 5,
          "status": "ok",
          "timestamp": 1627045834226,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "naMhoovz4mpF",
        "outputId": "d5a7fefe-147e-4a23-d32a-66d30eafce41"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "['1.0000000000000000', '2.0000000000000000']\n"
          ]
        }
      ],
      "source": [
        "print(['%0.16f'% x for x in np.array([1, 2])])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6hip4KtTaLQM"
      },
      "source": [
        "# Swaption pricing"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Bp-bbRAyLn1Y"
      },
      "outputs": [],
      "source": [
        "import enum\n",
        "\n",
        "from tf_quant_finance.math import pde\n",
        "from tf_quant_finance.models import utils\n",
        "from tf_quant_finance.models import valuation_method as vm\n",
        "from tf_quant_finance.models.hjm import gaussian_hjm\n",
        "from tf_quant_finance.models.hjm import quasi_gaussian_hjm\n",
        "from tf_quant_finance.models.hjm import swaption_util\n",
        "\n",
        "\n",
        "@enum.unique\n",
        "class ValuationMethod(enum.Enum):\n",
        "  \"\"\"Swaption valuation methods.\n",
        "\n",
        "  * `ANALYTIC`: Analytic valuation.\n",
        "  * `MONTE_CARLO`: Valuation using Monte carlo simulations.\n",
        "  * `FINITE_DIFFERENCE`: Valuation using finite difference.\n",
        "  \"\"\"\n",
        "  ANALYTIC = 1\n",
        "  MONTE_CARLO = 2\n",
        "  FINITE_DIFFERENCE = 3\n",
        "\n",
        "\n",
        "# Points smaller than this are merged together in FD time grid\n",
        "_PDE_TIME_GRID_TOL = 1e-7\n",
        "\n",
        "\n",
        "def price(*,\n",
        "          expiries,\n",
        "          fixed_leg_payment_times,\n",
        "          fixed_leg_daycount_fractions,\n",
        "          fixed_leg_coupon,\n",
        "          reference_rate_fn,\n",
        "          num_hjm_factors,\n",
        "          mean_reversion,\n",
        "          volatility,\n",
        "          times=None,\n",
        "          time_step=None,\n",
        "          num_time_steps=None,\n",
        "          curve_times=None,\n",
        "          corr_matrix=None,\n",
        "          notional=None,\n",
        "          is_payer_swaption=None,\n",
        "          valuation_method=vm.ValuationMethod.MONTE_CARLO,\n",
        "          num_samples=1,\n",
        "          random_type=None,\n",
        "          seed=None,\n",
        "          skip=0,\n",
        "          time_step_finite_difference=None,\n",
        "          num_time_steps_finite_difference=None,\n",
        "          num_grid_points_finite_difference=101,\n",
        "          dtype=None,\n",
        "          name=None):\n",
        "  \"\"\"Calculates the price of European swaptions using the HJM model.\n",
        "\n",
        "  A European Swaption is a contract that gives the holder an option to enter a\n",
        "  swap contract at a future date at a prespecified fixed rate. A swaption that\n",
        "  grants the holder the right to pay fixed rate and receive floating rate is\n",
        "  called a payer swaption while the swaption that grants the holder the right to\n",
        "  receive fixed and pay floating payments is called the receiver swaption.\n",
        "  Typically the start date (or the inception date) of the swap coincides with\n",
        "  the expiry of the swaption. Mid-curve swaptions are currently not supported\n",
        "  (b/160061740).\n",
        "\n",
        "  This implementation uses the HJM model to numerically value the swaption via\n",
        "  Monte-Carlo. For more information on the formulation of the HJM model, see\n",
        "  quasi_gaussian_hjm.py.\n",
        "\n",
        "  #### Example\n",
        "\n",
        "  ````python\n",
        "  import numpy as np\n",
        "  import tensorflow.compat.v2 as tf\n",
        "  import tf_quant_finance as tff\n",
        "\n",
        "  dtype = tf.float64\n",
        "\n",
        "  # Price 1y x 1y swaption with quarterly payments using Monte Carlo\n",
        "  # simulations.\n",
        "  expiries = np.array([1.0])\n",
        "  fixed_leg_payment_times = np.array([1.25, 1.5, 1.75, 2.0])\n",
        "  fixed_leg_daycount_fractions = 0.25 * np.ones_like(fixed_leg_payment_times)\n",
        "  fixed_leg_coupon = 0.011 * np.ones_like(fixed_leg_payment_times)\n",
        "  zero_rate_fn = lambda x: 0.01 * tf.ones_like(x, dtype=dtype)\n",
        "  mean_reversion = [0.03]\n",
        "  volatility = [0.02]\n",
        "\n",
        "  price = tff.models.hjm.swaption_price(\n",
        "      expiries=expiries,\n",
        "      fixed_leg_payment_times=fixed_leg_payment_times,\n",
        "      fixed_leg_daycount_fractions=fixed_leg_daycount_fractions,\n",
        "      fixed_leg_coupon=fixed_leg_coupon,\n",
        "      reference_rate_fn=zero_rate_fn,\n",
        "      notional=100.,\n",
        "      num_hjm_factors=1,\n",
        "      mean_reversion=mean_reversion,\n",
        "      volatility=volatility,\n",
        "      valuation_method=tff.model.ValuationMethod.MONTE_CARLO,\n",
        "      num_samples=500000,\n",
        "      time_step=0.1,\n",
        "      random_type=tff.math.random.RandomType.STATELESS_ANTITHETIC,\n",
        "      seed=[1, 2])\n",
        "  # Expected value: [[0.716]]\n",
        "  ````\n",
        "\n",
        "\n",
        "  #### References:\n",
        "    [1]: D. Brigo, F. Mercurio. Interest Rate Models-Theory and Practice.\n",
        "    Second Edition. 2007. Section 6.7, page 237.\n",
        "\n",
        "  Args:\n",
        "    expiries: A real `Tensor` of any shape and dtype. The time to expiration of\n",
        "      the swaptions. The shape of this input determines the number (and shape)\n",
        "      of swaptions to be priced and the shape of the output.\n",
        "    fixed_leg_payment_times: A real `Tensor` of the same dtype as `expiries`.\n",
        "      The payment times for each payment in the fixed leg. The shape of this\n",
        "      input should be `expiries.shape + [n]` where `n` denotes the number of\n",
        "      fixed payments in each leg. The `fixed_leg_payment_times` should be\n",
        "      greater-than or equal-to the corresponding expiries.\n",
        "    fixed_leg_daycount_fractions: A real `Tensor` of the same dtype and\n",
        "      compatible shape as `fixed_leg_payment_times`. The daycount fractions for\n",
        "      each payment in the fixed leg.\n",
        "    fixed_leg_coupon: A real `Tensor` of the same dtype and compatible shape as\n",
        "      `fixed_leg_payment_times`. The fixed rate for each payment in the fixed\n",
        "      leg.\n",
        "    reference_rate_fn: A Python callable that accepts expiry time as a real\n",
        "      `Tensor` and returns a `Tensor` of shape `input_shape +\n",
        "      [num_hjm_factors]`. Returns the continuously compounded zero rate at the\n",
        "      present time for the input expiry time.\n",
        "    num_hjm_factors: A Python scalar which corresponds to the number of factors\n",
        "      in the HJM model to be used for pricing.\n",
        "    mean_reversion: A real positive `Tensor` of shape `[num_hjm_factors]`.\n",
        "      Corresponds to the mean reversion rate of each factor.\n",
        "    volatility: A real positive `Tensor` of the same `dtype` and shape as\n",
        "      `mean_reversion` or a callable with the following properties: (a)  The\n",
        "        callable should accept a scalar `Tensor` `t` and a 1-D `Tensor` `r(t)`\n",
        "        of shape `[num_samples]` and returns a 2-D `Tensor` of shape\n",
        "        `[num_samples, num_hjm_factors]`. The variable `t`  stands for time and\n",
        "        `r(t)` is the short rate at time `t`.  The function returns the\n",
        "        instantaneous volatility `sigma(t) = sigma(t, r(t))`. When `volatility`\n",
        "        is specified as a real `Tensor`, each factor is assumed to have a\n",
        "        constant instantaneous volatility  and the  model is effectively a\n",
        "        Gaussian HJM model. Corresponds to the instantaneous volatility of each\n",
        "        factor.\n",
        "    times: An optional rank 1 `Tensor` of increasing positive real values. The\n",
        "      times at which Monte Carlo simulations are performed. Relevant when\n",
        "      swaption valuation is done using Monte Calro simulations.\n",
        "      Default value: `None` in which case simulation times are computed based\n",
        "      on either `time_step` or `num_time_steps` inputs.\n",
        "    time_step: Optional scalar real `Tensor`. Maximal distance between time\n",
        "      grid points in Euler scheme. Relevant when Euler scheme is used for\n",
        "      simulation. This input or `num_time_steps` are required when valuation\n",
        "      method is Monte Carlo.\n",
        "      Default Value: `None`.\n",
        "    num_time_steps: An optional scalar integer `Tensor` - a total number of\n",
        "      time steps during Monte Carlo simulations. The maximal distance betwen\n",
        "      points in grid is bounded by\n",
        "      `times[-1] / (num_time_steps - times.shape[0])`.\n",
        "      Either this or `time_step` should be supplied when the valuation method\n",
        "      is Monte Carlo.\n",
        "      Default value: `None`.\n",
        "    curve_times: An optional rank 1 `Tensor` of positive real values. The\n",
        "      maturities at which spot discount curve is computed during simulations.\n",
        "      Default value: `None` in which case `curve_times` is computed based on\n",
        "      swaption expities and `fixed_leg_payments_times` inputs.\n",
        "    corr_matrix: A `Tensor` of shape `[num_hjm_factors, num_hjm_factors]` and\n",
        "      the same `dtype` as `mean_reversion`. Specifies the correlation between\n",
        "      HJM factors.\n",
        "      Default value: `None` in which case the factors are assumed to be\n",
        "        uncorrelated.\n",
        "    notional: An optional `Tensor` of same dtype and compatible shape as\n",
        "      `strikes`specifying the notional amount for the underlying swaps.\n",
        "       Default value: None in which case the notional is set to 1.\n",
        "    is_payer_swaption: A boolean `Tensor` of a shape compatible with `expiries`.\n",
        "      Indicates whether the swaption is a payer (if True) or a receiver (if\n",
        "      False) swaption. If not supplied, payer swaptions are assumed.\n",
        "    valuation_method: An enum of type `ValuationMethod` specifying\n",
        "      the method to be used for swaption valuation. Currently the valuation is\n",
        "      supported using `MONTE_CARLO` and `FINITE_DIFFERENCE` methods. Valuation\n",
        "      using finite difference is only supported for Gaussian HJM models, i.e.\n",
        "      for models with constant mean-reversion rate and time-dependent\n",
        "      volatility.\n",
        "      Default value: `ValuationMethod.MONTE_CARLO`, in which case\n",
        "      swaption valuation is done using Monte Carlo simulations.\n",
        "    num_samples: Positive scalar `int32` `Tensor`. The number of simulation\n",
        "      paths during Monte-Carlo valuation. This input is ignored during analytic\n",
        "      valuation.\n",
        "      Default value: The default value is 1.\n",
        "    random_type: Enum value of `RandomType`. The type of (quasi)-random number\n",
        "      generator to use to generate the simulation paths. This input is relevant\n",
        "      only for Monte-Carlo valuation and ignored during analytic valuation.\n",
        "      Default value: `None` which maps to the standard pseudo-random numbers.\n",
        "    seed: Seed for the random number generator. The seed is only relevant if\n",
        "      `random_type` is one of `[STATELESS, PSEUDO, HALTON_RANDOMIZED,\n",
        "      PSEUDO_ANTITHETIC, STATELESS_ANTITHETIC]`. For `PSEUDO`,\n",
        "      `PSEUDO_ANTITHETIC` and `HALTON_RANDOMIZED` the seed should be an Python\n",
        "      integer. For `STATELESS` and  `STATELESS_ANTITHETIC` must be supplied as\n",
        "      an integer `Tensor` of shape `[2]`. This input is relevant only for\n",
        "      Monte-Carlo valuation and ignored during analytic valuation.\n",
        "      Default value: `None` which means no seed is set.\n",
        "    skip: `int32` 0-d `Tensor`. The number of initial points of the Sobol or\n",
        "      Halton sequence to skip. Used only when `random_type` is 'SOBOL',\n",
        "      'HALTON', or 'HALTON_RANDOMIZED', otherwise ignored.\n",
        "      Default value: `0`.\n",
        "    time_step_finite_difference: Optional scalar real `Tensor`. Spacing between\n",
        "      time grid points in finite difference discretization. This input is only\n",
        "      relevant for valuation using finite difference.\n",
        "      Default value: `None`. If `num_time_steps_finite_difference` is also\n",
        "      unspecified then a `time_step` corresponding to 100 discretization steps\n",
        "      is used.\n",
        "    num_time_steps_finite_difference: Optional scalar real `Tensor`. Number of\n",
        "      time grid points in finite difference discretization. This input is only\n",
        "      relevant for valuation using finite difference.\n",
        "      Default value: `None`. If `time_step_finite_difference` is also\n",
        "      unspecified, then 100 time steps are used.\n",
        "    num_grid_points_finite_difference: Optional scalar real `Tensor`. Number of\n",
        "      spatial grid points per dimension. Currently, we construct an uniform grid\n",
        "      for spatial discretization. This input is only relevant for valuation\n",
        "      using finite difference.\n",
        "      Default value: 101.\n",
        "    dtype: The default dtype to use when converting values to `Tensor`s.\n",
        "      Default value: `None` which means that default dtypes inferred by\n",
        "        TensorFlow are used.\n",
        "    name: Python string. The name to give to the ops created by this function.\n",
        "      Default value: `None` which maps to the default name `hjm_swaption_price`.\n",
        "\n",
        "  Returns:\n",
        "    A `Tensor` of real dtype and shape expiries.shape + [1]\n",
        "    containing the computed swaption prices. For swaptions that have reset in\n",
        "    the past (expiries\u003c0), the function sets the corresponding option prices to\n",
        "    0.0.\n",
        "  \"\"\"\n",
        "\n",
        "  # TODO(b/160061740): Extend the functionality to support mid-curve swaptions.\n",
        "  name = name or 'hjm_swaption_price'\n",
        "  with tf.name_scope(name):\n",
        "    expiries = tf.convert_to_tensor(expiries, dtype=dtype, name='expiries')\n",
        "    dtype = dtype or expiries.dtype\n",
        "    fixed_leg_payment_times = tf.convert_to_tensor(\n",
        "        fixed_leg_payment_times, dtype=dtype, name='fixed_leg_payment_times')\n",
        "    fixed_leg_daycount_fractions = tf.convert_to_tensor(\n",
        "        fixed_leg_daycount_fractions,\n",
        "        dtype=dtype,\n",
        "        name='fixed_leg_daycount_fractions')\n",
        "    fixed_leg_coupon = tf.convert_to_tensor(\n",
        "        fixed_leg_coupon, dtype=dtype, name='fixed_leg_coupon')\n",
        "    notional = tf.convert_to_tensor(notional, dtype=dtype, name='notional')\n",
        "    notional = tf.expand_dims(\n",
        "        tf.broadcast_to(notional, expiries.shape), axis=-1)\n",
        "    if is_payer_swaption is None:\n",
        "      is_payer_swaption = True\n",
        "    is_payer_swaption = tf.convert_to_tensor(\n",
        "        is_payer_swaption, dtype=tf.bool, name='is_payer_swaption')\n",
        "\n",
        "    output_shape = expiries.shape.as_list() + [1]\n",
        "    # Add a dimension corresponding to multiple cashflows in a swap\n",
        "    if expiries.shape.rank == fixed_leg_payment_times.shape.rank - 1:\n",
        "      expiries = tf.expand_dims(expiries, axis=-1)\n",
        "    elif expiries.shape.rank \u003c fixed_leg_payment_times.shape.rank - 1:\n",
        "      raise ValueError('Swaption expiries not specified for all swaptions '\n",
        "                       'in the batch. Expected rank {} but received {}.'.format(\n",
        "                           fixed_leg_payment_times.shape.rank - 1,\n",
        "                           expiries.shape.rank))\n",
        "\n",
        "    # Expected shape: batch_shape + [m], where m is the number of fixed leg\n",
        "    # payments per underlying swap. This is the same as\n",
        "    # fixed_leg_payment_times.shape\n",
        "    #\n",
        "    # We need to explicitly use tf.repeat because we need to price\n",
        "    # batch_shape + [m] bond options with different strikes along the last\n",
        "    # dimension.\n",
        "    expiries = tf.repeat(\n",
        "        expiries, tf.shape(fixed_leg_payment_times)[-1], axis=-1)\n",
        "\n",
        "    if valuation_method == vm.ValuationMethod.FINITE_DIFFERENCE:\n",
        "      model = gaussian_hjm.GaussianHJM(\n",
        "          num_hjm_factors,\n",
        "          mean_reversion=mean_reversion,\n",
        "          volatility=volatility,\n",
        "          initial_discount_rate_fn=reference_rate_fn,\n",
        "          corr_matrix=corr_matrix,\n",
        "          dtype=dtype)\n",
        "\n",
        "      batch_shape = expiries.shape.as_list()[:-1] or [1]\n",
        "      return _bermudan_swaption_fd(\n",
        "          batch_shape,\n",
        "          model,\n",
        "          # Add a dimension to denote ONE exercise date\n",
        "          tf.expand_dims(expiries, axis=-2),\n",
        "          fixed_leg_payment_times,\n",
        "          fixed_leg_daycount_fractions,\n",
        "          fixed_leg_coupon,\n",
        "          notional,\n",
        "          is_payer_swaption,\n",
        "          time_step_finite_difference,\n",
        "          num_time_steps_finite_difference,\n",
        "          num_grid_points_finite_difference,\n",
        "          name + '_fd',\n",
        "          dtype)\n",
        "    elif valuation_method == vm.ValuationMethod.MONTE_CARLO:\n",
        "      # Monte-Carlo pricing\n",
        "      model = quasi_gaussian_hjm.QuasiGaussianHJM(\n",
        "          num_hjm_factors,\n",
        "          mean_reversion=mean_reversion,\n",
        "          volatility=volatility,\n",
        "          initial_discount_rate_fn=reference_rate_fn,\n",
        "          corr_matrix=corr_matrix,\n",
        "          dtype=dtype)\n",
        "\n",
        "      return _european_swaption_mc(\n",
        "          output_shape, model, expiries, fixed_leg_payment_times,\n",
        "          fixed_leg_daycount_fractions, fixed_leg_coupon, notional,\n",
        "          is_payer_swaption, times, time_step, num_time_steps, curve_times,\n",
        "          num_samples, random_type, skip, seed, dtype, name + '_mc')\n",
        "    else:\n",
        "      raise ValueError('Swaption Valuation using {} is not supported'.format(\n",
        "          str(valuation_method)))\n",
        "\n",
        "\n",
        "def _european_swaption_mc(output_shape, model, expiries,\n",
        "                          fixed_leg_payment_times, fixed_leg_daycount_fractions,\n",
        "                          fixed_leg_coupon, notional, is_payer_swaption, times,\n",
        "                          time_step, num_time_steps, curve_times, num_samples,\n",
        "                          random_type, skip, seed, dtype, name):\n",
        "  \"\"\"Price European swaptions using Monte-Carlo.\"\"\"\n",
        "  with tf.name_scope(name):\n",
        "    if (times is None) and (time_step is None) and (num_time_steps is None):\n",
        "      raise ValueError(\n",
        "          'One of `times`, `time_step` or `num_time_steps` must be '\n",
        "          'provided for simulation based swaption valuation.')\n",
        "\n",
        "    def _sample_discount_curve_path_fn(times, curve_times, num_samples):\n",
        "      p_t_tau, r_t, df = model.sample_discount_curve_paths(\n",
        "          times=times,\n",
        "          curve_times=curve_times,\n",
        "          num_samples=num_samples,\n",
        "          random_type=random_type,\n",
        "          time_step=time_step,\n",
        "          num_time_steps=num_time_steps,\n",
        "          seed=seed,\n",
        "          skip=skip)\n",
        "      p_t_tau = tf.expand_dims(p_t_tau, axis=-1)\n",
        "      r_t = tf.expand_dims(r_t, axis=-1)\n",
        "      df = tf.expand_dims(df, axis=-1)\n",
        "      return p_t_tau, r_t, df\n",
        "\n",
        "    payoff_discount_factors, payoff_bond_price = (\n",
        "        swaption_util.discount_factors_and_bond_prices_from_samples(\n",
        "            expiries=expiries,\n",
        "            payment_times=fixed_leg_payment_times,\n",
        "            sample_discount_curve_paths_fn=_sample_discount_curve_path_fn,\n",
        "            num_samples=num_samples,\n",
        "            times=times,\n",
        "            curve_times=curve_times,\n",
        "            dtype=dtype))\n",
        "\n",
        "    # Add an axis corresponding to `dim`\n",
        "    fixed_leg_pv = tf.expand_dims(\n",
        "        fixed_leg_coupon * fixed_leg_daycount_fractions,\n",
        "        axis=-1) * payoff_bond_price\n",
        "\n",
        "    # Sum fixed coupon payments within each swap.\n",
        "    # Here, axis=-2 is the payments axis - i.e. summing over all payments; and\n",
        "    # the last axis is the `dim` axis, as explained in comment above\n",
        "    # `fixed_leg_pv` (Note that for HJM the dim of this axis is 1 always).\n",
        "    fixed_leg_pv = tf.math.reduce_sum(fixed_leg_pv, axis=-2)\n",
        "    float_leg_pv = 1.0 - payoff_bond_price[..., -1, :]\n",
        "    payoff_swap = payoff_discount_factors[..., -1, :] * (\n",
        "        float_leg_pv - fixed_leg_pv)\n",
        "    payoff_swap = tf.where(is_payer_swaption, payoff_swap, -1.0 * payoff_swap)\n",
        "    payoff_swaption = tf.math.maximum(payoff_swap, 0.0)\n",
        "    option_value = tf.reshape(\n",
        "        tf.math.reduce_mean(payoff_swaption, axis=0), output_shape)\n",
        "\n",
        "    return notional * option_value\n",
        "\n",
        "\n",
        "def _bermudan_swaption_fd(batch_shape, model, exercise_times,\n",
        "                          fixed_leg_payment_times, fixed_leg_daycount_fractions,\n",
        "                          fixed_leg_coupon, notional, is_payer_swaption,\n",
        "                          time_step_fd, num_time_steps_fd, num_grid_points_fd,\n",
        "                          name, dtype):\n",
        "  \"\"\"Price Bermudan swaptions using finite difference.\"\"\"\n",
        "  with tf.name_scope(name):\n",
        "    dim = model.dim()\n",
        "    x_min = -0.5\n",
        "    x_max = 0.5\n",
        "    # grid.shape = (num_grid_points,2)\n",
        "    grid = pde.grids.uniform_grid(\n",
        "        minimums=[x_min] * dim,\n",
        "        maximums=[x_max] * dim,\n",
        "        sizes=[num_grid_points_fd] * dim,\n",
        "        dtype=dtype)\n",
        "\n",
        "    # TODO(b/186876306): Remove dynamic shapes.\n",
        "    pde_time_grid, pde_time_grid_dt = _create_pde_time_grid(\n",
        "        exercise_times, time_step_fd, num_time_steps_fd, dtype)\n",
        "    maturities, unique_maturities, maturities_shape = (\n",
        "        _create_termstructure_maturities(fixed_leg_payment_times))\n",
        "\n",
        "    num_maturities = tf.shape(unique_maturities)[-1]\n",
        "    x_meshgrid = _coord_grid_to_mesh_grid(grid)\n",
        "    meshgrid_shape = tf.shape(x_meshgrid)\n",
        "    broadcasted_maturities = tf.expand_dims(unique_maturities, axis=0)\n",
        "\n",
        "    num_grid_points = tf.math.reduce_prod(meshgrid_shape[1:])\n",
        "    shape_to_broadcast = tf.concat(\n",
        "        [meshgrid_shape, [num_maturities]], axis=0)\n",
        "\n",
        "    # Reshape `state_x`, `maturities` to (num_grid_points, num_maturities)\n",
        "    state_x = tf.expand_dims(x_meshgrid, axis=-1)\n",
        "    state_x = tf.broadcast_to(state_x, shape_to_broadcast)\n",
        "    broadcasted_maturities = tf.broadcast_to(\n",
        "        broadcasted_maturities, shape_to_broadcast[1:])\n",
        "\n",
        "    def _get_swap_payoff(payoff_time):\n",
        "      broadcasted_exercise_times = tf.broadcast_to(\n",
        "          payoff_time, shape_to_broadcast[1:])\n",
        "\n",
        "      # Zero-coupon bond curve\n",
        "      zcb_curve = model.discount_bond_price(\n",
        "          tf.transpose(\n",
        "              tf.reshape(\n",
        "                  state_x, [dim, num_grid_points * num_maturities])),\n",
        "          tf.reshape(broadcasted_exercise_times, [-1]),\n",
        "          tf.reshape(broadcasted_maturities, [-1]))\n",
        "      zcb_curve = tf.reshape(\n",
        "          zcb_curve, [num_grid_points, num_maturities])\n",
        "\n",
        "      maturities_index = tf.searchsorted(\n",
        "          unique_maturities, tf.reshape(maturities, [-1]))\n",
        "\n",
        "      zcb_curve = tf.gather(zcb_curve, maturities_index, axis=-1)\n",
        "      # zcb_curve.shape = [num_grid_points] + [maturities_shape]\n",
        "      zcb_curve = tf.reshape(\n",
        "          zcb_curve, tf.concat([[num_grid_points], maturities_shape], axis=0))\n",
        "\n",
        "      # Shape after reduce_sum =\n",
        "      # (num_grid_points, batch_shape)\n",
        "      fixed_leg = tf.math.reduce_sum(\n",
        "          fixed_leg_coupon * fixed_leg_daycount_fractions * zcb_curve, axis=-1)\n",
        "      float_leg = 1.0 - zcb_curve[..., -1]\n",
        "      payoff_swap = float_leg - fixed_leg\n",
        "      payoff_swap = tf.where(is_payer_swaption, payoff_swap, -payoff_swap)\n",
        "      return tf.reshape(tf.transpose(\n",
        "          payoff_swap), tf.concat([batch_shape, meshgrid_shape[1:]], axis=0))\n",
        "\n",
        "    def _get_index(t, tensor_to_search):\n",
        "      t = tf.expand_dims(t, axis=-1)\n",
        "      index = tf.searchsorted(tensor_to_search, t - _PDE_TIME_GRID_TOL, 'right')\n",
        "      y = tf.gather(tensor_to_search, index)\n",
        "      return tf.where(tf.math.abs(t - y) \u003c _PDE_TIME_GRID_TOL, index, -1)[0]\n",
        "\n",
        "    sum_x_meshgrid = tf.math.reduce_sum(x_meshgrid, axis=0)\n",
        "\n",
        "    def _is_exercise_time(t):\n",
        "      return tf.reduce_any(tf.where(\n",
        "          tf.math.abs(exercise_times[..., -1] - t) \u003c _PDE_TIME_GRID_TOL,\n",
        "          True, False), axis=-1)\n",
        "\n",
        "    def _discounting_fn(t, grid):\n",
        "      del grid\n",
        "      f_0_t = (model._instant_forward_rate_fn(t))  # pylint: disable=protected-access\n",
        "      return sum_x_meshgrid + f_0_t\n",
        "\n",
        "    def _final_value():\n",
        "      t = pde_time_grid[-1]\n",
        "      payoff_swap = tf.nn.relu(_get_swap_payoff(t))\n",
        "      is_ex_time = _is_exercise_time(t)\n",
        "      return tf.where(tf.reshape(is_ex_time, tf.concat(\n",
        "          [batch_shape, [1] * dim], axis=0)), payoff_swap, 0.0)\n",
        "\n",
        "    def _values_transform_fn(t, grid, value_grid):\n",
        "      zero = tf.zeros_like(value_grid)\n",
        "      is_ex_time = _is_exercise_time(t)\n",
        "      def _at_least_one_swaption_pays():\n",
        "        payoff_swap = tf.nn.relu(_get_swap_payoff(t))\n",
        "        return tf.where(tf.reshape(is_ex_time, tf.concat(\n",
        "            [batch_shape, [1] * dim], axis=0)), payoff_swap, zero)\n",
        "      v_star = tf.cond(tf.math.reduce_any(is_ex_time),\n",
        "                       _at_least_one_swaption_pays, lambda: zero)\n",
        "      return grid, tf.maximum(value_grid, v_star)\n",
        "\n",
        "    # TODO(b/186876306): Use piecewise constant func here.\n",
        "    def _pde_time_step(t):\n",
        "      index = _get_index(t, pde_time_grid)\n",
        "      cond = lambda i, dt: tf.math.logical_and(dt \u003c _PDE_TIME_GRID_TOL, i \u003e -1)\n",
        "\n",
        "      def _fn(i, dt):\n",
        "        del dt\n",
        "        return i + 1, pde_time_grid_dt[i + 1]\n",
        "\n",
        "      _, dt = tf.while_loop(cond, _fn, (index, pde_time_grid_dt[index]))\n",
        "      return dt\n",
        "\n",
        "    # Use default boundary conditions, d^2V/dx_i^2 = 0\n",
        "    boundary_conditions = [(None, None) for i in range(dim)]\n",
        "    # res[0] contains the swaption prices.\n",
        "    # res[0].shape = batch_shape + [num_grid_points] * dim\n",
        "    res = model.fd_solver_backward(\n",
        "        pde_time_grid[-1],\n",
        "        0.0,\n",
        "        grid,\n",
        "        values_grid=_final_value(),\n",
        "        time_step=_pde_time_step,\n",
        "        boundary_conditions=boundary_conditions,\n",
        "        values_transform_fn=_values_transform_fn,\n",
        "        discounting=_discounting_fn,\n",
        "        dtype=dtype)\n",
        "\n",
        "    idx = tf.searchsorted(\n",
        "        tf.convert_to_tensor(grid),\n",
        "        tf.expand_dims(tf.convert_to_tensor([0.0] * dim, dtype=dtype), axis=-1))\n",
        "    # idx.shape = (dim, 1)\n",
        "    idx = tf.squeeze(idx) if dim \u003e 1 else tf.expand_dims(idx, axis=-1)\n",
        "\n",
        "    option_value = res[0]\n",
        "    for i in range(dim - 1, -1, -1):\n",
        "      option_value = tf.gather(option_value, idx[i], axis=-1)\n",
        "    # output_shape = batch_shape + [1]\n",
        "    return notional * tf.expand_dims(\n",
        "        tf.reshape(option_value, batch_shape), axis=-1)\n",
        "\n",
        "\n",
        "def _coord_grid_to_mesh_grid(coord_grid):\n",
        "  if len(coord_grid) == 1:\n",
        "    return tf.expand_dims(coord_grid[0], 0)\n",
        "  x_meshgrid = tf.stack(values=tf.meshgrid(*coord_grid, indexing='ij'), axis=-1)\n",
        "  perm = [len(coord_grid)] + list(range(len(coord_grid)))\n",
        "  return tf.transpose(x_meshgrid, perm=perm)\n",
        "\n",
        "\n",
        "def _create_pde_time_grid(exercise_times, time_step_fd, num_time_steps_fd,\n",
        "                          dtype):\n",
        "  \"\"\"Create PDE time grid.\"\"\"\n",
        "  with tf.name_scope('create_pde_time_grid'):\n",
        "    exercise_times, _ = tf.unique(tf.reshape(exercise_times, shape=[-1]))\n",
        "    if num_time_steps_fd is not None:\n",
        "      num_time_steps_fd = tf.convert_to_tensor(\n",
        "          num_time_steps_fd, dtype=tf.int32, name='num_time_steps_fd')\n",
        "      time_step_fd = tf.math.reduce_max(exercise_times) / tf.cast(\n",
        "          num_time_steps_fd, dtype=dtype)\n",
        "    if time_step_fd is None and num_time_steps_fd is None:\n",
        "      num_time_steps_fd = 100\n",
        "\n",
        "    pde_time_grid, _, _ = utils.prepare_grid(\n",
        "        times=exercise_times,\n",
        "        time_step=time_step_fd,\n",
        "        dtype=dtype,\n",
        "        num_time_steps=num_time_steps_fd)\n",
        "    pde_time_grid_dt = pde_time_grid[1:] - pde_time_grid[:-1]\n",
        "    pde_time_grid_dt = tf.concat([[100.0], pde_time_grid_dt], axis=-1)\n",
        "\n",
        "    return pde_time_grid, pde_time_grid_dt\n",
        "\n",
        "\n",
        "def _create_termstructure_maturities(fixed_leg_payment_times):\n",
        "  \"\"\"Create maturities needed for termstructure simulations.\"\"\"\n",
        "\n",
        "  with tf.name_scope('create_termstructure_maturities'):\n",
        "    maturities = fixed_leg_payment_times\n",
        "    maturities_shape = tf.shape(maturities)\n",
        "\n",
        "    # We should eventually remove tf.unique, but keeping it for now because\n",
        "    # PDE solvers are not xla compatible in TFF currently.\n",
        "    unique_maturities, _ = tf.unique(tf.reshape(maturities, shape=[-1]))\n",
        "    unique_maturities = tf.sort(unique_maturities, name='sort_maturities')\n",
        "\n",
        "    return maturities, unique_maturities, maturities_shape"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OWwDd2Qk3QyV"
      },
      "source": [
        "## Example"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 6491,
          "status": "ok",
          "timestamp": 1627045847672,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "nSvfWcNPvGTw",
        "outputId": "b8f6e55d-c671-40e3-a39c-704e934f5c6a"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "\u003ctf.Tensor: shape=(2, 1), dtype=float64, numpy=\n",
              "array([[0.58909314],\n",
              "       [0.80141135]])\u003e"
            ]
          },
          "execution_count": 27,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "swaption_price = price\n",
        "dtype = tf.float64\n",
        "\n",
        "expiries = np.array([1.002739726, 2.002739726])\n",
        "fixed_leg_payment_times = np.array([[1.249315068,\t1.498630137,\t1.750684932,\t2.002739726],\n",
        "                                    [2.249315068,\t2.498630137,\t2.750684932,\t3.002739726]])\n",
        "fixed_leg_daycount_fractions = np.array([[0.2465753425,\t0.2493150685,\t0.2520547945,\t0.2520547945],\n",
        "                                         [0.2465753425,\t0.2493150685,\t0.2520547945,\t0.2520547945]])\n",
        "fixed_leg_coupon = 0.011 * np.ones_like(fixed_leg_payment_times)\n",
        "zero_rate_fn = lambda x: 0.01 * tf.ones_like(x, dtype=dtype)\n",
        "\n",
        "mu = [0.03, 0.15]\n",
        "vol = [0.01, 0.015]\n",
        "\n",
        "\n",
        "hjm_price = swaption_price(\n",
        "    expiries=expiries,\n",
        "    fixed_leg_payment_times=fixed_leg_payment_times,\n",
        "    fixed_leg_daycount_fractions=fixed_leg_daycount_fractions,\n",
        "    fixed_leg_coupon=fixed_leg_coupon,\n",
        "    reference_rate_fn=zero_rate_fn,\n",
        "    notional=100.,\n",
        "    num_hjm_factors=len(mu),\n",
        "    mean_reversion=mu,\n",
        "    volatility=vol,\n",
        "    # corr_matrix=[[1, 0.0], [0.0, 1]],\n",
        "    valuation_method=vm.ValuationMethod.FINITE_DIFFERENCE,\n",
        "    time_step_finite_difference=0.05,\n",
        "    num_grid_points_finite_difference=251,\n",
        "    time_step=0.05,\n",
        "    num_samples=1000000,\n",
        "    dtype=dtype)\n",
        "hjm_price"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-Z-2hbK5gZ4J"
      },
      "source": [
        "### Using quantlib"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 6260,
          "status": "ok",
          "timestamp": 1627045876515,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "nehPuYKO6Qor",
        "outputId": "70edd185-492b-4bb6-e743-f2aa4eb79ddc"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "price using HW 1.007148261731575\n",
            "price using G2 0.8029012153434956\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "0.010012511157488175"
            ]
          },
          "execution_count": 30,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "import QuantLib as ql\n",
        "from QuantLib import *\n",
        "\n",
        "frequency_enum, settle_date = 4, Date(1, 1, 2020)\n",
        "expiry_date = Date(1,1,2022)\n",
        "midcurve_expiry_date = Date(1,7,2020)\n",
        "maturity_date = Date(1, 1, 2023)\n",
        "face_amount = 100.0\n",
        "settlement_days = 0\n",
        "fixing_days = 0\n",
        "\n",
        "calendar = NullCalendar()\n",
        "todays_date = Date(1,1,2020)\n",
        "Settings.instance().evaluationDate = todays_date\n",
        "\n",
        "spotDates = [ql.Date(1, 1, 2020), ql.Date(1, 1, 2021), ql.Date(1, 1, 2022), ql.Date(1, 1, 2030)]\n",
        "spotRates = [0.01, 0.01, 0.01, 0.01]\n",
        "\n",
        "dayCount = ql.Actual365Fixed()\n",
        "calendar = ql.UnitedStates()\n",
        "interpolation = ql.Linear()\n",
        "compounding = ql.Compounded\n",
        "compoundingFrequency = ql.Continuous\n",
        "spotCurve = ql.ZeroCurve(spotDates, spotRates, dayCount)\n",
        "spotCurveHandle = ql.YieldTermStructureHandle(spotCurve)\n",
        "\n",
        "\n",
        "index = IborIndex('USD Libor', Period(3, Months), settlement_days, USDCurrency(), NullCalendar(),\n",
        "                  Unadjusted, False, Actual365Fixed(), spotCurveHandle)\n",
        "#index.addFixing(ql.Date(15,1,2021), 0.006556)\n",
        "schedule = Schedule(expiry_date,\n",
        "                    maturity_date, Period(frequency_enum),\n",
        "                    NullCalendar(),\n",
        "                    Unadjusted, Unadjusted,\n",
        "                    DateGeneration.Forward, False)\n",
        "\n",
        "strike = 0.011\n",
        "ibor_leg = ql.IborLeg([face_amount], schedule, index)\n",
        "fixed_leg = ql.FixedRateLeg(schedule, Actual365Fixed(), [face_amount], [strike])\n",
        "\n",
        "swap = ql.VanillaSwap(ql.VanillaSwap.Payer, face_amount, schedule, strike, Actual365Fixed(), schedule, index, 0.0, Actual365Fixed()) \n",
        "exercise_date = ql.EuropeanExercise(expiry_date)\n",
        "swaption = ql.Swaption(swap, exercise_date)\n",
        "\n",
        "\n",
        "# price using hull-white\n",
        "model = ql.HullWhite(spotCurveHandle, a=0.03, sigma=0.02)\n",
        "engine = ql.JamshidianSwaptionEngine(model, spotCurveHandle)\n",
        "swaption.setPricingEngine(engine)\n",
        "print(\"price using HW\", swaption.NPV())\n",
        "\n",
        "# price using G2\n",
        "model = ql.G2(spotCurveHandle, a=0.03, sigma=0.01, b=0.15, eta=0.015, rho=0.0)\n",
        "engine = ql.FdG2SwaptionEngine(model, 20, 500, 500)\n",
        "swaption.setPricingEngine(engine)\n",
        "print(\"price using G2\", swaption.NPV())\n",
        "\n",
        "#  additional debugging\n",
        "swap.setPricingEngine(ql.DiscountingSwapEngine(spotCurveHandle))\n",
        "swap.fairRate()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "N_kpGLZsIPKj"
      },
      "source": [
        "## Example - Time dependent vol"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_hpElryGXFt2"
      },
      "outputs": [],
      "source": [
        "swaption_price = price\n",
        "dtype = tf.float64\n",
        "\n",
        "expiries = np.array([1.002739726, 1.002739726])\n",
        "fixed_leg_payment_times = np.array([[1.249315068,\t1.498630137,\t1.750684932,\t2.002739726],\n",
        "                                    [1.249315068,\t1.498630137,\t1.750684932,\t2.002739726]])\n",
        "fixed_leg_daycount_fractions = np.array([[0.2465753425,\t0.2493150685,\t0.2520547945,\t0.2520547945],\n",
        "                                         [0.2465753425,\t0.2493150685,\t0.2520547945,\t0.2520547945]])\n",
        "fixed_leg_coupon = 0.011 * np.ones_like(fixed_leg_payment_times)\n",
        "zero_rate_fn = lambda x: 0.01 * tf.ones_like(x, dtype=dtype)\n",
        "\n",
        "mu = [0.03, 0.15]\n",
        "vol = piecewise.PiecewiseConstantFunc(\n",
        "    [[0.5],[0.5]], [[0.01, 0.015], [0.015, 0.02]], dtype=dtype)\n",
        "\n",
        "\n",
        "hjm_price = swaption_price(\n",
        "    expiries=[expiries[0]],\n",
        "    fixed_leg_payment_times=fixed_leg_payment_times[0],\n",
        "    fixed_leg_daycount_fractions=fixed_leg_daycount_fractions[0],\n",
        "    fixed_leg_coupon=fixed_leg_coupon[0],\n",
        "    reference_rate_fn=zero_rate_fn,\n",
        "    notional=100.,\n",
        "    num_hjm_factors=len(mu),\n",
        "    mean_reversion=mu,\n",
        "    volatility=vol,\n",
        "    # corr_matrix=[[1, 0.0], [0.0, 1]],\n",
        "    valuation_method=ValuationMethod.MONTE_CARLO,\n",
        "    time_step_finite_difference=0.05,\n",
        "    num_grid_points_finite_difference=501,\n",
        "    time_step=0.05,\n",
        "    num_samples=2000000,\n",
        "    dtype=dtype)\n",
        "hjm_price"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 336,
          "status": "ok",
          "timestamp": 1627045897958,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "9CvxLRz1Iz1E",
        "outputId": "178ca900-b8b9-4075-9b98-1f0b920c939d"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "\u003ctf.Tensor: shape=(2, 1), dtype=float64, numpy=\n",
              "array([[0.015],\n",
              "       [0.02 ]])\u003e"
            ]
          },
          "execution_count": 32,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "vol([0.50001])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "executionInfo": {
          "elapsed": 5,
          "status": "ok",
          "timestamp": 1627045899343,
          "user": {
            "displayName": "Cyril Chimisov",
            "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64",
            "userId": "02803093032097482871"
          },
          "user_tz": -60
        },
        "id": "OZblNNnCa4tm",
        "outputId": "7d706001-c1fb-4ce0-d247-5940b1e59e4e"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "\u003ctf.Tensor: shape=(4,), dtype=float32, numpy=array([6., 6., 6., 6.], dtype=float32)\u003e"
            ]
          },
          "execution_count": 33,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "aa = tf.ones((2,3,4))\n",
        "tf.reduce_sum(aa, axis=[0, 1])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "qY70jvYiWpTj"
      },
      "outputs": [],
      "source": [
        ""
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "Exact_Gaussian_HJM_WIP.ipynb",
      "provenance": [],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.7.9"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
